@pencil-agent/nano-pencil 1.6.2 → 1.6.3

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.
@@ -4,6 +4,7 @@
4
4
  * Manages MCP client lifecycle and tool integration.
5
5
  */
6
6
  import { MCPClient } from "./mcp/mcp-client.js";
7
+ import { loadMCPTools } from "./mcp/mcp-adapter.js";
7
8
  import { listEnabledMCPServers } from "./mcp/mcp-config.js";
8
9
  export class MCPManager {
9
10
  client;
@@ -25,8 +26,7 @@ export class MCPManager {
25
26
  }
26
27
  }
27
28
  // Load tools from all servers
28
- // TODO: MCP tool integration disabled due to type compatibility issues
29
- // this.tools = await loadMCPTools(this.client);
29
+ this.tools = await loadMCPTools(this.client);
30
30
  }
31
31
  /**
32
32
  * Get all MCP tools as NanoPencil ToolDefinitions
package/dist/core/sdk.js CHANGED
@@ -234,20 +234,20 @@ export async function createAgentSession(options = {}) {
234
234
  }
235
235
  // Initialize MCP if enabled (before creating AgentSession)
236
236
  let mcpManager;
237
- // TODO: MCP tool integration disabled due to type compatibility issues
238
- // Will be re-enabled after refactoring tool adapter
239
- // const mcpTools: ToolDefinition[] = [];
237
+ const mcpTools = [];
240
238
  if (options.enableMCP) {
241
239
  try {
242
240
  mcpManager = new MCPManager();
243
241
  await mcpManager.initialize();
244
- // mcpTools.push(...mcpManager.getTools());
242
+ mcpTools.push(...mcpManager.getTools());
245
243
  time("mcp.initialize");
246
244
  }
247
245
  catch (error) {
248
246
  console.warn(`Failed to initialize MCP: ${error}`);
249
247
  }
250
248
  }
249
+ // Merge MCP tools with custom tools
250
+ const allCustomTools = [...(options.customTools || []), ...mcpTools];
251
251
  // Initialize Soul if enabled (before creating AgentSession)
252
252
  let soulManager;
253
253
  if (isSoulEnabled(options)) {
@@ -273,7 +273,7 @@ export async function createAgentSession(options = {}) {
273
273
  cwd,
274
274
  scopedModels: options.scopedModels,
275
275
  resourceLoader,
276
- customTools: options.customTools,
276
+ customTools: allCustomTools,
277
277
  modelRegistry,
278
278
  initialActiveToolNames,
279
279
  extensionRunnerRef,
package/dist/main.js CHANGED
@@ -4,7 +4,7 @@
4
4
  * This file handles CLI argument parsing and translates them into
5
5
  * createAgentSession() options. The SDK does the heavy lifting.
6
6
  */
7
- import { modelsAreEqual, supportsXhigh } from "@mariozechner/pi-ai";
7
+ import { modelsAreEqual, supportsXhigh, } from "@mariozechner/pi-ai";
8
8
  import chalk from "chalk";
9
9
  import { join } from "path";
10
10
  import { createInterface } from "readline";
@@ -19,17 +19,17 @@ import { DEFAULT_THINKING_LEVEL } from "./core/defaults.js";
19
19
  import { exportFromFile } from "./core/export-html/index.js";
20
20
  import { KeybindingsManager } from "./core/keybindings.js";
21
21
  import { ModelRegistry } from "./core/model-registry.js";
22
- import { resolveCliModel, resolveModelScope } from "./core/model-resolver.js";
22
+ import { resolveCliModel, resolveModelScope, } from "./core/model-resolver.js";
23
23
  import { DefaultPackageManager } from "./core/package-manager.js";
24
24
  import { DefaultResourceLoader } from "./core/resource-loader.js";
25
- import { createAgentSession } from "./core/sdk.js";
25
+ import { createAgentSession, } from "./core/sdk.js";
26
26
  import { SessionManager } from "./core/session-manager.js";
27
27
  import { SettingsManager } from "./core/settings-manager.js";
28
28
  import { printTimings, time } from "./core/timings.js";
29
29
  import { allTools } from "./core/tools/index.js";
30
30
  import { runMigrations, showDeprecationWarnings } from "./migrations.js";
31
31
  import { InteractiveMode, runPrintMode, runRpcMode } from "./modes/index.js";
32
- import { initTheme, stopThemeWatcher } from "./modes/interactive/theme/theme.js";
32
+ import { initTheme, stopThemeWatcher, } from "./modes/interactive/theme/theme.js";
33
33
  import { ensureNanopencilCodingPlanAuth, ensureNanopencilDefaultConfig, NANOPENCIL_DEFAULT_PROVIDER, } from "./nanopencil-defaults.js";
34
34
  import { getNanopencilDefaultExtensionPaths } from "./pencil-mem-integration.js";
35
35
  /**
@@ -65,7 +65,9 @@ function reportSettingsErrors(settingsManager, context) {
65
65
  function isTruthyEnvFlag(value) {
66
66
  if (!value)
67
67
  return false;
68
- return value === "1" || value.toLowerCase() === "true" || value.toLowerCase() === "yes";
68
+ return (value === "1" ||
69
+ value.toLowerCase() === "true" ||
70
+ value.toLowerCase() === "yes");
69
71
  }
70
72
  function getPackageCommandUsage(command) {
71
73
  switch (command) {
@@ -82,56 +84,59 @@ function getPackageCommandUsage(command) {
82
84
  function printPackageCommandHelp(command) {
83
85
  switch (command) {
84
86
  case "install":
85
- console.log(`${chalk.bold("Usage:")}
86
- ${getPackageCommandUsage("install")}
87
-
88
- Install a package and add it to settings.
89
-
90
- Options:
91
- -l, --local Install project-locally (.pi/settings.json)
92
-
93
- Examples:
94
- ${APP_NAME} install npm:@foo/bar
95
- ${APP_NAME} install git:github.com/user/repo
96
- ${APP_NAME} install git:git@github.com:user/repo
97
- ${APP_NAME} install https://github.com/user/repo
98
- ${APP_NAME} install ssh://git@github.com/user/repo
99
- ${APP_NAME} install ./local/path
87
+ console.log(`${chalk.bold("Usage:")}
88
+ ${getPackageCommandUsage("install")}
89
+
90
+ Install a package and add it to settings.
91
+
92
+ Options:
93
+ -l, --local Install project-locally (.pi/settings.json)
94
+
95
+ Examples:
96
+ ${APP_NAME} install npm:@foo/bar
97
+ ${APP_NAME} install git:github.com/user/repo
98
+ ${APP_NAME} install git:git@github.com:user/repo
99
+ ${APP_NAME} install https://github.com/user/repo
100
+ ${APP_NAME} install ssh://git@github.com/user/repo
101
+ ${APP_NAME} install ./local/path
100
102
  `);
101
103
  return;
102
104
  case "remove":
103
- console.log(`${chalk.bold("Usage:")}
104
- ${getPackageCommandUsage("remove")}
105
-
106
- Remove a package and its source from settings.
107
-
108
- Options:
109
- -l, --local Remove from project settings (.pi/settings.json)
110
-
111
- Example:
112
- ${APP_NAME} remove npm:@foo/bar
105
+ console.log(`${chalk.bold("Usage:")}
106
+ ${getPackageCommandUsage("remove")}
107
+
108
+ Remove a package and its source from settings.
109
+
110
+ Options:
111
+ -l, --local Remove from project settings (.pi/settings.json)
112
+
113
+ Example:
114
+ ${APP_NAME} remove npm:@foo/bar
113
115
  `);
114
116
  return;
115
117
  case "update":
116
- console.log(`${chalk.bold("Usage:")}
117
- ${getPackageCommandUsage("update")}
118
-
119
- Update installed packages.
120
- If <source> is provided, only that package is updated.
118
+ console.log(`${chalk.bold("Usage:")}
119
+ ${getPackageCommandUsage("update")}
120
+
121
+ Update installed packages.
122
+ If <source> is provided, only that package is updated.
121
123
  `);
122
124
  return;
123
125
  case "list":
124
- console.log(`${chalk.bold("Usage:")}
125
- ${getPackageCommandUsage("list")}
126
-
127
- List installed packages from user and project settings.
126
+ console.log(`${chalk.bold("Usage:")}
127
+ ${getPackageCommandUsage("list")}
128
+
129
+ List installed packages from user and project settings.
128
130
  `);
129
131
  return;
130
132
  }
131
133
  }
132
134
  function parsePackageCommand(args) {
133
135
  const [command, ...rest] = args;
134
- if (command !== "install" && command !== "remove" && command !== "update" && command !== "list") {
136
+ if (command !== "install" &&
137
+ command !== "remove" &&
138
+ command !== "update" &&
139
+ command !== "list") {
135
140
  return undefined;
136
141
  }
137
142
  let local = false;
@@ -178,7 +183,8 @@ async function handlePackageCommand(args) {
178
183
  return true;
179
184
  }
180
185
  const source = options.source;
181
- if ((options.command === "install" || options.command === "remove") && !source) {
186
+ if ((options.command === "install" || options.command === "remove") &&
187
+ !source) {
182
188
  console.error(chalk.red(`Missing ${options.command} source.`));
183
189
  console.error(chalk.dim(`Usage: ${getPackageCommandUsage(options.command)}`));
184
190
  process.exitCode = 1;
@@ -188,7 +194,11 @@ async function handlePackageCommand(args) {
188
194
  const agentDir = getAgentDir();
189
195
  const settingsManager = SettingsManager.create(cwd, agentDir);
190
196
  reportSettingsErrors(settingsManager, "package command");
191
- const packageManager = new DefaultPackageManager({ cwd, agentDir, settingsManager });
197
+ const packageManager = new DefaultPackageManager({
198
+ cwd,
199
+ agentDir,
200
+ settingsManager,
201
+ });
192
202
  packageManager.setProgressCallback((event) => {
193
203
  if (event.type === "start") {
194
204
  process.stdout.write(chalk.dim(`${event.message}\n`));
@@ -203,7 +213,9 @@ async function handlePackageCommand(args) {
203
213
  return true;
204
214
  case "remove": {
205
215
  await packageManager.remove(source, { local: options.local });
206
- const removed = packageManager.removeSourceFromSettings(source, { local: options.local });
216
+ const removed = packageManager.removeSourceFromSettings(source, {
217
+ local: options.local,
218
+ });
207
219
  if (!removed) {
208
220
  console.error(chalk.red(`No matching package found for ${source}`));
209
221
  process.exitCode = 1;
@@ -269,7 +281,9 @@ async function prepareInitialMessage(parsed, autoResizeImages) {
269
281
  if (parsed.fileArgs.length === 0) {
270
282
  return {};
271
283
  }
272
- const { text, images } = await processFileArguments(parsed.fileArgs, { autoResizeImages });
284
+ const { text, images } = await processFileArguments(parsed.fileArgs, {
285
+ autoResizeImages,
286
+ });
273
287
  let initialMessage;
274
288
  if (parsed.messages.length > 0) {
275
289
  initialMessage = text + parsed.messages[0];
@@ -289,7 +303,9 @@ async function prepareInitialMessage(parsed, autoResizeImages) {
289
303
  */
290
304
  async function resolveSessionPath(sessionArg, cwd, sessionDir) {
291
305
  // If it looks like a file path, use as-is
292
- if (sessionArg.includes("/") || sessionArg.includes("\\") || sessionArg.endsWith(".jsonl")) {
306
+ if (sessionArg.includes("/") ||
307
+ sessionArg.includes("\\") ||
308
+ sessionArg.endsWith(".jsonl")) {
293
309
  return { type: "path", path: sessionArg };
294
310
  }
295
311
  // Try to match as session ID in current project first
@@ -357,7 +373,7 @@ async function createSessionManager(parsed, cwd) {
357
373
  // Default case (new session) returns undefined, SDK will create one
358
374
  return undefined;
359
375
  }
360
- function buildSessionOptions(parsed, scopedModels, sessionManager, modelRegistry, settingsManager) {
376
+ async function buildSessionOptions(parsed, scopedModels, sessionManager, modelRegistry, settingsManager) {
361
377
  const options = {};
362
378
  let cliThinkingFromModel = false;
363
379
  if (sessionManager) {
@@ -389,12 +405,19 @@ function buildSessionOptions(parsed, scopedModels, sessionManager, modelRegistry
389
405
  }
390
406
  }
391
407
  }
392
- if (!options.model && scopedModels.length > 0 && !parsed.continue && !parsed.resume) {
408
+ if (!options.model &&
409
+ scopedModels.length > 0 &&
410
+ !parsed.continue &&
411
+ !parsed.resume) {
393
412
  // Check if saved default is in scoped models - use it if so, otherwise first scoped model
394
413
  const savedProvider = settingsManager.getDefaultProvider();
395
414
  const savedModelId = settingsManager.getDefaultModel();
396
- const savedModel = savedProvider && savedModelId ? modelRegistry.find(savedProvider, savedModelId) : undefined;
397
- const savedInScope = savedModel ? scopedModels.find((sm) => modelsAreEqual(sm.model, savedModel)) : undefined;
415
+ const savedModel = savedProvider && savedModelId
416
+ ? modelRegistry.find(savedProvider, savedModelId)
417
+ : undefined;
418
+ const savedInScope = savedModel
419
+ ? scopedModels.find((sm) => modelsAreEqual(sm.model, savedModel))
420
+ : undefined;
398
421
  if (savedInScope) {
399
422
  options.model = savedInScope.model;
400
423
  // Use thinking level from scoped model config if explicitly set
@@ -438,6 +461,13 @@ function buildSessionOptions(parsed, scopedModels, sessionManager, modelRegistry
438
461
  else if (parsed.tools) {
439
462
  options.tools = parsed.tools.map((name) => allTools[name]);
440
463
  }
464
+ // Enable MCP by default if there are enabled MCP servers
465
+ // Users can disable with --no-mcp flag if needed
466
+ const { listEnabledMCPServers } = await import("./core/mcp/mcp-config.js");
467
+ const enabledMcpServers = listEnabledMCPServers();
468
+ if (enabledMcpServers.length > 0) {
469
+ options.enableMCP = true;
470
+ }
441
471
  return { options, cliThinkingFromModel };
442
472
  }
443
473
  async function handleConfigCommand(args) {
@@ -448,7 +478,11 @@ async function handleConfigCommand(args) {
448
478
  const agentDir = getAgentDir();
449
479
  const settingsManager = SettingsManager.create(cwd, agentDir);
450
480
  reportSettingsErrors(settingsManager, "config command");
451
- const packageManager = new DefaultPackageManager({ cwd, agentDir, settingsManager });
481
+ const packageManager = new DefaultPackageManager({
482
+ cwd,
483
+ agentDir,
484
+ settingsManager,
485
+ });
452
486
  const resolvedPaths = await packageManager.resolve();
453
487
  await selectConfig({
454
488
  resolvedPaths,
@@ -488,14 +522,20 @@ export async function main(args) {
488
522
  }
489
523
  }
490
524
  const modelRegistry = new ModelRegistry(authStorage, getModelsPath(), APP_NAME === "nanopencil"
491
- ? { useOnlyCustomModels: true, allowOptionalApiKeyForProvider: NANOPENCIL_DEFAULT_PROVIDER }
525
+ ? {
526
+ useOnlyCustomModels: true,
527
+ allowOptionalApiKeyForProvider: NANOPENCIL_DEFAULT_PROVIDER,
528
+ }
492
529
  : {});
493
530
  const defaultExtPaths = APP_NAME === "nanopencil" ? getNanopencilDefaultExtensionPaths() : [];
494
531
  const resourceLoader = new DefaultResourceLoader({
495
532
  cwd,
496
533
  agentDir,
497
534
  settingsManager,
498
- additionalExtensionPaths: [...defaultExtPaths, ...(firstPass.extensions ?? [])],
535
+ additionalExtensionPaths: [
536
+ ...defaultExtPaths,
537
+ ...(firstPass.extensions ?? []),
538
+ ],
499
539
  additionalSkillPaths: firstPass.skills,
500
540
  additionalPromptTemplatePaths: firstPass.promptTemplates,
501
541
  additionalThemePaths: firstPass.themes,
@@ -525,7 +565,8 @@ export async function main(args) {
525
565
  }
526
566
  // Apply pending provider registrations from extensions immediately
527
567
  // so they're available for model resolution before AgentSession is created
528
- for (const { name, config } of extensionsResult.runtime.pendingProviderRegistrations) {
568
+ for (const { name, config } of extensionsResult.runtime
569
+ .pendingProviderRegistrations) {
529
570
  modelRegistry.registerProvider(name, config);
530
571
  }
531
572
  extensionsResult.runtime.pendingProviderRegistrations = [];
@@ -592,7 +633,8 @@ export async function main(args) {
592
633
  if (APP_NAME === "nanopencil" && settingsManager.getTheme() === undefined) {
593
634
  settingsManager.setTheme("warm");
594
635
  }
595
- initTheme(settingsManager.getTheme() ?? (APP_NAME === "nanopencil" ? "warm" : undefined), isInteractive);
636
+ initTheme(settingsManager.getTheme() ??
637
+ (APP_NAME === "nanopencil" ? "warm" : undefined), isInteractive);
596
638
  // Show deprecation warnings in interactive mode
597
639
  if (isInteractive && deprecationWarnings.length > 0) {
598
640
  await showDeprecationWarnings(deprecationWarnings);
@@ -616,7 +658,7 @@ export async function main(args) {
616
658
  }
617
659
  sessionManager = SessionManager.open(selectedPath);
618
660
  }
619
- const { options: sessionOptions, cliThinkingFromModel } = buildSessionOptions(parsed, scopedModels, sessionManager, modelRegistry, settingsManager);
661
+ const { options: sessionOptions, cliThinkingFromModel } = await buildSessionOptions(parsed, scopedModels, sessionManager, modelRegistry, settingsManager);
620
662
  sessionOptions.authStorage = authStorage;
621
663
  sessionOptions.modelRegistry = modelRegistry;
622
664
  sessionOptions.resourceLoader = resourceLoader;
@@ -655,7 +697,8 @@ export async function main(args) {
655
697
  await runRpcMode(session);
656
698
  }
657
699
  else if (isInteractive) {
658
- if (scopedModels.length > 0 && (parsed.verbose || !settingsManager.getQuietStartup())) {
700
+ if (scopedModels.length > 0 &&
701
+ (parsed.verbose || !settingsManager.getQuietStartup())) {
659
702
  const modelList = scopedModels
660
703
  .map((sm) => {
661
704
  const thinkingStr = sm.thinkingLevel ? `:${sm.thinkingLevel}` : "";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pencil-agent/nano-pencil",
3
- "version": "1.6.2",
3
+ "version": "1.6.3",
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": {