pi-code 1.0.38 → 1.0.40

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.
@@ -16,6 +16,7 @@ import * as fs from 'node:fs'
16
16
  import * as path from 'node:path'
17
17
 
18
18
  import { claudeConfigDir } from './config-dir.js'
19
+ import { readManagedSettings } from './managed-settings.js'
19
20
 
20
21
  export interface InstalledPlugin {
21
22
  name: string
@@ -169,16 +170,30 @@ export function installedPlugins(home: string, extraSettingsFiles: string[] = []
169
170
  return plugins
170
171
  }
171
172
 
173
+ /** The plugin's effective enablement per Claude's precedence: a managed
174
+ * enabledPlugins entry force-enables or blocks, then the user's setting, then the
175
+ * manifest's defaultEnabled, which defaults to true ("starts in an enabled state
176
+ * when the user has not set one"). */
177
+ function pluginEnabled(qualified: string, pluginDir: string, enabled: Record<string, boolean>, manifest: Record<string, unknown>): boolean {
178
+ const managedEntry = readManagedSettings().enabledPlugins
179
+ if (managedEntry !== null && typeof managedEntry === 'object') {
180
+ const managedState = (managedEntry as Record<string, unknown>)[qualified] ?? (managedEntry as Record<string, unknown>)[pluginDir]
181
+ if (typeof managedState === 'boolean') return managedState
182
+ }
183
+ const userState = enabled[qualified] ?? enabled[pluginDir]
184
+ if (typeof userState === 'boolean') return userState
185
+ return manifest.defaultEnabled !== false
186
+ }
187
+
172
188
  /** Resolve one cached plugin directory into an enabled InstalledPlugin, or null to skip
173
- * it: not turned on in settings, or no version directory on disk yet. */
189
+ * it: turned off by managed/user settings or defaultEnabled, or no version yet. */
174
190
  function resolvePlugin(home: string, cacheDir: string, marketplace: string, pluginDir: string, enabled: Record<string, boolean>, configs: Record<string, Record<string, string>>): InstalledPlugin | null {
175
191
  const qualified = `${pluginDir}@${marketplace}`
176
- const state = enabled[qualified] ?? enabled[pluginDir]
177
- if (state !== true) return null
178
192
  const version = newestVersion(listDirs(path.join(cacheDir, marketplace, pluginDir)))
179
193
  if (!version) return null
180
194
  const root = path.join(cacheDir, marketplace, pluginDir, version)
181
195
  const manifest = readJson(path.join(root, '.claude-plugin', 'plugin.json'))
196
+ if (!pluginEnabled(qualified, pluginDir, enabled, manifest)) return null
182
197
  const name = typeof manifest.name === 'string' && manifest.name.length > 0 ? manifest.name : pluginDir
183
198
  // Claude: "{id} is the plugin identifier with characters outside a-z, A-Z, 0-9,
184
199
  // _, and - replaced by -", one dash per character, underscores kept.
@@ -595,6 +595,34 @@ export default async function mcpExtension(pi: ExtensionAPI) {
595
595
  status.clear()
596
596
  })
597
597
 
598
+ // Claude references MCP resources with @server:uri mentions, fetched into the
599
+ // conversation when referenced. Only mentions naming a connected server expand;
600
+ // anything else (an email, a handle) stays untouched.
601
+ pi.on('input', async (event) => {
602
+ if (event.source === 'extension') return
603
+ const mentions = [...event.text.matchAll(/@([A-Za-z0-9_-]+):(\S+)/g)].filter((match) => clients.has(match[1]))
604
+ if (mentions.length === 0) return
605
+ const sections: string[] = []
606
+ for (const match of mentions) {
607
+ const [, server, uri] = match
608
+ try {
609
+ const client = clients.get(server)
610
+ if (!client) continue
611
+ const wall = callTimeoutMs()
612
+ const result = await withTimeout(client.readResource({ uri }, callRequestOptions(wall, callTuning(server))), wall, `read ${uri}`)
613
+ const text = (result.contents as Array<{ text?: string }>)
614
+ .map((entry) => entry.text)
615
+ .filter((value): value is string => typeof value === 'string')
616
+ .join('\n')
617
+ if (text) sections.push(capForContext(`<mcp-resource server="${server}" uri="${uri}">\n${text}\n</mcp-resource>`))
618
+ } catch {
619
+ // An unreadable resource leaves the mention as plain text.
620
+ }
621
+ }
622
+ if (sections.length === 0) return
623
+ return { action: 'transform', text: `${event.text}\n\n${sections.join('\n\n')}` }
624
+ })
625
+
598
626
  pi.registerCommand('mcp', {
599
627
  description: 'Show MCP server status and tools',
600
628
  handler: async (_args, ctx) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-code",
3
- "version": "1.0.38",
3
+ "version": "1.0.40",
4
4
  "description": "Claude Code experience for the pi coding agent: reads your .claude config (rules, commands, skills, hooks, output styles, MCP servers, agents) and adds todo, checkpoints, memory, web, and subagents",
5
5
  "keywords": [
6
6
  "pi",