pi-code 1.0.26 → 1.0.27
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.
- package/extensions/mcp/config.ts +9 -1
- package/extensions/mcp/index.ts +15 -5
- package/extensions/mcp/transport.ts +90 -24
- package/package.json +1 -1
package/extensions/mcp/config.ts
CHANGED
|
@@ -20,6 +20,10 @@ export interface StdioServerConfig {
|
|
|
20
20
|
timeout?: number
|
|
21
21
|
/** Plugin servers alias their tools mcp__plugin_<plugin>_<server>__<tool>. */
|
|
22
22
|
aliasPrefix?: string
|
|
23
|
+
/** Root of the plugin that supplied this server; exported as CLAUDE_PLUGIN_ROOT. */
|
|
24
|
+
pluginRoot?: string
|
|
25
|
+
/** Loaded from the project scope, whose helpers run credential-stripped. */
|
|
26
|
+
projectScope?: boolean
|
|
23
27
|
}
|
|
24
28
|
|
|
25
29
|
export interface HttpServerConfig {
|
|
@@ -35,6 +39,10 @@ export interface HttpServerConfig {
|
|
|
35
39
|
timeout?: number
|
|
36
40
|
/** Plugin servers alias their tools mcp__plugin_<plugin>_<server>__<tool>. */
|
|
37
41
|
aliasPrefix?: string
|
|
42
|
+
/** Root of the plugin that supplied this server; exported as CLAUDE_PLUGIN_ROOT. */
|
|
43
|
+
pluginRoot?: string
|
|
44
|
+
/** Loaded from the project scope, whose helpers run credential-stripped. */
|
|
45
|
+
projectScope?: boolean
|
|
38
46
|
}
|
|
39
47
|
|
|
40
48
|
export type ServerConfig = StdioServerConfig | HttpServerConfig
|
|
@@ -200,7 +208,7 @@ export function loadPluginServers(plugins: InstalledPlugin[], projectDir?: strin
|
|
|
200
208
|
for (const plugin of plugins) {
|
|
201
209
|
for (const [name, config] of Object.entries(rawPluginServerEntries(plugin))) {
|
|
202
210
|
const substituted = substitutedPluginServer(plugin, name, config, projectDir)
|
|
203
|
-
if (substituted) servers[name] = { ...substituted, aliasPrefix: `mcp__plugin_${fold(plugin.name)}_${fold(name)}__
|
|
211
|
+
if (substituted) servers[name] = { ...substituted, aliasPrefix: `mcp__plugin_${fold(plugin.name)}_${fold(name)}__`, pluginRoot: plugin.root }
|
|
204
212
|
}
|
|
205
213
|
}
|
|
206
214
|
return servers
|
package/extensions/mcp/index.ts
CHANGED
|
@@ -20,8 +20,9 @@
|
|
|
20
20
|
* environment plus its own `env` block, not the whole process environment.
|
|
21
21
|
*
|
|
22
22
|
* Servers advertising the `prompts` capability get their prompts registered as Claude's
|
|
23
|
-
* /mcp__<server>__<prompt> slash commands (
|
|
24
|
-
*
|
|
23
|
+
* /mcp__<server>__<prompt> slash commands (server-name characters outside A-Za-z0-9_-
|
|
24
|
+
* fold to underscores, args split on whitespace and mapped positionally, one token
|
|
25
|
+
* per declared argument); the prompt result drives a turn via
|
|
25
26
|
* sendUserMessage, exactly how custom slash commands do. Servers advertising `resources`
|
|
26
27
|
* make the global list_mcp_resources / read_mcp_resource tools available, mirroring
|
|
27
28
|
* Claude's automatic resource tools. Resource and prompt output rides the same
|
|
@@ -47,7 +48,7 @@ import { disabledServerNames, loadConfigFrom, loadPluginServers, loadUserScope,
|
|
|
47
48
|
import { collectServerResourceEntries, listAllPrompts, listAllTools, type McpToolInfo, resourceServerFilter } from './listing.js'
|
|
48
49
|
import { formatPromptCommandName, formatToolName, type McpContentBlock, type McpPromptInfo, mapContent, mapPromptArguments, normalizeSchema, promptMessageContent } from './mapping.js'
|
|
49
50
|
import { applyServerPolicy, loadManagedMcpServers, type McpPolicy, mcpAllowDeny, projectServerPolicy, splitByPolicy } from './policy.js'
|
|
50
|
-
import { type AuthUi, callRequestOptions, callTimeoutMs, connect, connectTimeoutMs, type ServerCallTuning, serverCallTuning, withTimeout } from './transport.js'
|
|
51
|
+
import { type AuthUi, callRequestOptions, callTimeoutMs, connect, connectTimeoutMs, type ServerCallTuning, type SessionDirs, serverCallTuning, withTimeout } from './transport.js'
|
|
51
52
|
|
|
52
53
|
export { managedSettingsPath, setManagedSettingsPath } from '../internal/managed-settings.js'
|
|
53
54
|
// Re-exports for consumers: the module split keeps the extension's public surface
|
|
@@ -87,6 +88,9 @@ export default async function mcpExtension(pi: ExtensionAPI) {
|
|
|
87
88
|
// Config per server name, kept for call-time timeout tuning: the idle tier follows
|
|
88
89
|
// the transport kind, and a declared per-server timeout governs the wall budget.
|
|
89
90
|
const serverConfigs = new Map<string, ServerConfig>()
|
|
91
|
+
// The session's directories, set at session_start before any connect: the launch
|
|
92
|
+
// directory answers roots/list, the project root feeds CLAUDE_PROJECT_DIR.
|
|
93
|
+
let sessionDirs: SessionDirs | undefined
|
|
90
94
|
const callTuning = (name: string): ServerCallTuning => {
|
|
91
95
|
const config = serverConfigs.get(name)
|
|
92
96
|
return config ? serverCallTuning(config) : {}
|
|
@@ -356,7 +360,7 @@ export default async function mcpExtension(pi: ExtensionAPI) {
|
|
|
356
360
|
pending.map(async ([name, config]) => {
|
|
357
361
|
warnOnTypelessUrl(name, config)
|
|
358
362
|
try {
|
|
359
|
-
const client = await connect(name, config, authUi)
|
|
363
|
+
const client = await connect(name, config, authUi, sessionDirs)
|
|
360
364
|
clients.set(name, client)
|
|
361
365
|
const tools = await withTimeout(listAllTools(client), connectTimeoutMs(), `list tools ${name}`)
|
|
362
366
|
registerTools(name, config, tools)
|
|
@@ -448,7 +452,10 @@ export default async function mcpExtension(pi: ExtensionAPI) {
|
|
|
448
452
|
// The stored project decision, read without prompting: consent recorded inside
|
|
449
453
|
// the project only counts once the project itself has been approved.
|
|
450
454
|
const projectPolicy = projectServerPolicy(ctx.cwd, os.homedir(), isProjectApprovedSilently(ctx))
|
|
451
|
-
|
|
455
|
+
// Tag the scope on each project server: a repository-supplied headersHelper runs
|
|
456
|
+
// with credential variables stripped, unlike a user-scope one.
|
|
457
|
+
const projectServers = Object.fromEntries(Object.entries(loadConfigFrom(projectConfigPaths(ctx.cwd))).map(([name, config]) => [name, { ...config, projectScope: true }]))
|
|
458
|
+
const { consented: consentedRaw, gated } = splitByPolicy(applyServerPolicy(projectServers, policy), projectPolicy)
|
|
452
459
|
// Claude's scope precedence is local over project: a name the local scope defines
|
|
453
460
|
// stays with the local (user-side) definition, so the project's entry is dropped
|
|
454
461
|
// here rather than allowed to shadow it.
|
|
@@ -480,6 +487,9 @@ export default async function mcpExtension(pi: ExtensionAPI) {
|
|
|
480
487
|
// withdrawn tool keeps its registration and surfaces the server's own error), which
|
|
481
488
|
// is why serverToolCount reads from `registered` to recover the true count here.
|
|
482
489
|
status.clear()
|
|
490
|
+
// Claude answers roots/list with the session's launch directory and exports the
|
|
491
|
+
// project root as CLAUDE_PROJECT_DIR to stdio servers; both derive from ctx.cwd.
|
|
492
|
+
sessionDirs = { projectDir: repoRoot(ctx.cwd) ?? ctx.cwd, launchDir: ctx.cwd }
|
|
483
493
|
const authUi = authUiFor(ctx)
|
|
484
494
|
// The allow/deny lists filter every scope, including a managed-mcp.json set. They
|
|
485
495
|
// merge from managed settings plus the trust-gated settings chain, as Claude
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import { execFile } from 'node:child_process'
|
|
8
|
+
import { pathToFileURL } from 'node:url'
|
|
8
9
|
// SSE is deprecated in favour of Streamable HTTP, but the SDK notes servers still on
|
|
9
10
|
// the old spec exist, so this stays as a fallback for the migration period.
|
|
10
11
|
import { type OAuthClientProvider, UnauthorizedError } from '@modelcontextprotocol/sdk/client/auth.js'
|
|
@@ -13,8 +14,9 @@ import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js' //
|
|
|
13
14
|
import { getDefaultEnvironment, StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js'
|
|
14
15
|
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js'
|
|
15
16
|
import { WebSocketClientTransport } from '@modelcontextprotocol/sdk/client/websocket.js'
|
|
17
|
+
import { ListRootsRequestSchema } from '@modelcontextprotocol/sdk/types.js'
|
|
16
18
|
import { FileOAuthProvider } from '../internal/mcp-oauth.js'
|
|
17
|
-
import { expandCwd, interpolateEnv, type ServerConfig, type StdioServerConfig } from './config.js'
|
|
19
|
+
import { expandCwd, type HttpServerConfig, interpolateEnv, type ServerConfig, type StdioServerConfig } from './config.js'
|
|
18
20
|
import { runInteractiveOAuth, serializeInteractiveOAuth } from './oauth-flow.js'
|
|
19
21
|
|
|
20
22
|
// Claude's MCP_TIMEOUT default: 30 seconds per connect attempt.
|
|
@@ -128,6 +130,67 @@ function isStdio(config: ServerConfig): config is StdioServerConfig {
|
|
|
128
130
|
return 'command' in config && (config.type === undefined || config.type === 'stdio')
|
|
129
131
|
}
|
|
130
132
|
|
|
133
|
+
/** The session's directories: the launch directory answers roots/list, and the
|
|
134
|
+
* project root becomes CLAUDE_PROJECT_DIR in a stdio server's environment. */
|
|
135
|
+
export interface SessionDirs {
|
|
136
|
+
projectDir: string
|
|
137
|
+
launchDir: string
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** A client that, like Claude, declares the roots capability and answers roots/list
|
|
141
|
+
* with the session's launch directory. pi's directory set is static, so no
|
|
142
|
+
* roots/list_changed notification is ever sent. */
|
|
143
|
+
function makeClient(session?: SessionDirs): Client {
|
|
144
|
+
if (!session) return new Client({ name: 'pi-code-mcp', version: '0.1.0' })
|
|
145
|
+
const client = new Client({ name: 'pi-code-mcp', version: '0.1.0' }, { capabilities: { roots: {} } })
|
|
146
|
+
client.setRequestHandler(ListRootsRequestSchema, () => ({ roots: [{ uri: pathToFileURL(session.launchDir).href }] }))
|
|
147
|
+
return client
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/** Claude's credential heuristic for helper environments: any name with TOKEN,
|
|
151
|
+
* SECRET, PASSWORD, KEY, or AUTH in it in either case (Git's GIT_CONFIG_KEY_<n>
|
|
152
|
+
* excepted), plus a fixed list of credential names outside the pattern. */
|
|
153
|
+
const CREDENTIAL_NAME_EXTRAS = new Set(['ANTHROPIC_CUSTOM_HEADERS'])
|
|
154
|
+
function isCredentialEnvName(name: string): boolean {
|
|
155
|
+
if (/^GIT_CONFIG_KEY_\d+$/.test(name)) return false
|
|
156
|
+
return /TOKEN|SECRET|PASSWORD|KEY|AUTH/i.test(name) || CREDENTIAL_NAME_EXTRAS.has(name)
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/** process.env with every credential-named value replaced by REDACTED, used to
|
|
160
|
+
* expand the url a helper is shown without handing it the credential. */
|
|
161
|
+
function redactedEnv(): NodeJS.ProcessEnv {
|
|
162
|
+
return Object.fromEntries(Object.entries(process.env).map(([key, value]) => [key, isCredentialEnvName(key) ? 'REDACTED' : value]))
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/** The environment a headersHelper runs with: Claude's CLAUDE_CODE_MCP_SERVER_NAME
|
|
166
|
+
* and CLAUDE_CODE_MCP_SERVER_URL (credential-expanded url parts REDACTED), plus
|
|
167
|
+
* CLAUDE_PLUGIN_ROOT for a plugin's server. A helper a repository or plugin
|
|
168
|
+
* supplies is a command the user did not write, so it runs without the
|
|
169
|
+
* credential-named variables; a user-scope helper keeps them. */
|
|
170
|
+
function helperEnv(name: string, config: HttpServerConfig): NodeJS.ProcessEnv {
|
|
171
|
+
const stripped = config.projectScope === true || config.pluginRoot !== undefined
|
|
172
|
+
const env: NodeJS.ProcessEnv = {}
|
|
173
|
+
for (const [key, value] of Object.entries(process.env)) {
|
|
174
|
+
if (stripped && isCredentialEnvName(key)) continue
|
|
175
|
+
env[key] = value
|
|
176
|
+
}
|
|
177
|
+
env.CLAUDE_CODE_MCP_SERVER_NAME = name
|
|
178
|
+
env.CLAUDE_CODE_MCP_SERVER_URL = interpolateEnv(config.url, redactedEnv())
|
|
179
|
+
if (config.pluginRoot !== undefined) env.CLAUDE_PLUGIN_ROOT = config.pluginRoot
|
|
180
|
+
return env
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/** The env a stdio server process starts with: the SDK allowlist, the config's own
|
|
184
|
+
* env block, and Claude's path variables (CLAUDE_PROJECT_DIR, and CLAUDE_PLUGIN_ROOT
|
|
185
|
+
* for a plugin's server). */
|
|
186
|
+
function stdioEnv(config: StdioServerConfig, fill: (value: string) => string, session?: SessionDirs): Record<string, string> {
|
|
187
|
+
const env: Record<string, string> = { ...getDefaultEnvironment() }
|
|
188
|
+
for (const [key, value] of Object.entries(config.env ?? {})) env[key] = fill(value)
|
|
189
|
+
if (session) env.CLAUDE_PROJECT_DIR = session.projectDir
|
|
190
|
+
if (config.pluginRoot !== undefined) env.CLAUDE_PLUGIN_ROOT = config.pluginRoot
|
|
191
|
+
return env
|
|
192
|
+
}
|
|
193
|
+
|
|
131
194
|
export async function withTimeout<T>(promise: Promise<T>, ms: number, label: string): Promise<T> {
|
|
132
195
|
let timer: ReturnType<typeof setTimeout> | undefined
|
|
133
196
|
const timeout = new Promise<never>((_, reject) => {
|
|
@@ -143,8 +206,8 @@ export async function withTimeout<T>(promise: Promise<T>, ms: number, label: str
|
|
|
143
206
|
}
|
|
144
207
|
}
|
|
145
208
|
|
|
146
|
-
export async function connect(name: string, config: ServerConfig, authUi?: AuthUi): Promise<Client> {
|
|
147
|
-
const client =
|
|
209
|
+
export async function connect(name: string, config: ServerConfig, authUi?: AuthUi, session?: SessionDirs): Promise<Client> {
|
|
210
|
+
const client = makeClient(session)
|
|
148
211
|
// Names referenced by ${VAR} with no value and no default, gathered across this
|
|
149
212
|
// server's interpolated fields so the connect can warn once rather than fail with a
|
|
150
213
|
// mystery 401 or a command that lost an argument.
|
|
@@ -157,12 +220,10 @@ export async function connect(name: string, config: ServerConfig, authUi?: AuthU
|
|
|
157
220
|
// Start from the SDK's allowlist (PATH, HOME, SHELL, ...) rather than the whole
|
|
158
221
|
// process env: a server should not receive ANTHROPIC_API_KEY or GITHUB_TOKEN just
|
|
159
222
|
// for being launched. A server that needs a variable names it in its own env block.
|
|
160
|
-
const env: Record<string, string> = { ...getDefaultEnvironment() }
|
|
161
|
-
for (const [key, value] of Object.entries(config.env ?? {})) env[key] = fill(value)
|
|
162
223
|
const transport = new StdioClientTransport({
|
|
163
224
|
command: fill(config.command),
|
|
164
225
|
args: (config.args ?? []).map((arg) => fill(arg)),
|
|
165
|
-
env,
|
|
226
|
+
env: stdioEnv(config, fill, session),
|
|
166
227
|
cwd: expandCwd(config.cwd),
|
|
167
228
|
stderr: 'ignore',
|
|
168
229
|
})
|
|
@@ -192,26 +253,30 @@ export async function connect(name: string, config: ServerConfig, authUi?: AuthU
|
|
|
192
253
|
if (token) headers.Authorization = `Bearer ${token}`
|
|
193
254
|
// A headersHelper generates connect-time headers for non-OAuth auth schemes; its
|
|
194
255
|
// JSON stdout merges over the static headers.
|
|
195
|
-
if (config.headersHelper) Object.assign(headers, await runHeadersHelper(fill(config.headersHelper)))
|
|
256
|
+
if (config.headersHelper) Object.assign(headers, await runHeadersHelper(fill(config.headersHelper), helperEnv(name, config)))
|
|
196
257
|
warnMissing()
|
|
258
|
+
// Claude: a configured Authorization header, whether static, a bearer token, or
|
|
259
|
+
// helper output, is the server's authentication; there is no OAuth fallback for it.
|
|
260
|
+
const configuredAuth = Boolean(token) || Object.keys(headers).some((header) => header.toLowerCase() === 'authorization')
|
|
197
261
|
const sseTransport = (authProvider?: OAuthClientProvider) => new SSEClientTransport(url, { requestInit: { headers }, authProvider }) // NOSONAR: explicitly declared or deliberate legacy transport
|
|
198
262
|
if (config.type === 'sse') {
|
|
199
|
-
return await connectHttpFamily(name, config, sseTransport, `connect ${name} (sse)`,
|
|
263
|
+
return await connectHttpFamily(name, config, sseTransport, `connect ${name} (sse)`, configuredAuth, authUi, session)
|
|
200
264
|
}
|
|
201
265
|
try {
|
|
202
|
-
return await connectHttpFamily(name, config, (authProvider) => new StreamableHTTPClientTransport(url, { requestInit: { headers }, authProvider }), `connect ${name}`,
|
|
266
|
+
return await connectHttpFamily(name, config, (authProvider) => new StreamableHTTPClientTransport(url, { requestInit: { headers }, authProvider }), `connect ${name}`, configuredAuth, authUi, session)
|
|
203
267
|
} catch (error) {
|
|
204
268
|
// An explicitly declared streamable transport must not silently degrade to SSE.
|
|
205
269
|
if (config.type !== undefined || isUnauthorized(error)) throw error
|
|
206
|
-
return await connectHttpFamily(name, config, sseTransport, `connect ${name} (sse)`,
|
|
270
|
+
return await connectHttpFamily(name, config, sseTransport, `connect ${name} (sse)`, configuredAuth, authUi, session)
|
|
207
271
|
}
|
|
208
272
|
}
|
|
209
273
|
|
|
210
|
-
/** Run a headersHelper command and parse its JSON stdout into headers
|
|
211
|
-
* a 10s timeout yields no extra headers
|
|
212
|
-
|
|
274
|
+
/** Run a headersHelper command and parse its JSON stdout into headers, under the
|
|
275
|
+
* environment helperEnv built. A failure or a 10s timeout yields no extra headers
|
|
276
|
+
* rather than blocking the connection. */
|
|
277
|
+
function runHeadersHelper(command: string, env: NodeJS.ProcessEnv): Promise<Record<string, string>> {
|
|
213
278
|
return new Promise((resolve) => {
|
|
214
|
-
execFile('/bin/sh', ['-c', command], { timeout: 10_000 }, (error, stdout) => {
|
|
279
|
+
execFile('/bin/sh', ['-c', command], { timeout: 10_000, env }, (error, stdout) => {
|
|
215
280
|
resolve(error ? {} : parseHelperHeaders(stdout))
|
|
216
281
|
})
|
|
217
282
|
})
|
|
@@ -229,12 +294,12 @@ export interface AuthUi {
|
|
|
229
294
|
export class OAuthRequiredError extends Error {}
|
|
230
295
|
|
|
231
296
|
/** Whether a connect failure is an authentication problem: the SDK's own
|
|
232
|
-
* UnauthorizedError, a transport error carrying HTTP 401
|
|
233
|
-
*
|
|
234
|
-
* or our own marker. */
|
|
297
|
+
* UnauthorizedError, a transport error carrying HTTP 401 or 403 (Claude: "either
|
|
298
|
+
* status code flags it" for OAuth), or our own marker. */
|
|
235
299
|
export function isUnauthorized(error: unknown): boolean {
|
|
236
300
|
if (error instanceof UnauthorizedError || error instanceof OAuthRequiredError) return true
|
|
237
|
-
|
|
301
|
+
const code = typeof error === 'object' && error !== null ? (error as { code?: unknown }).code : undefined
|
|
302
|
+
return code === 401 || code === 403
|
|
238
303
|
}
|
|
239
304
|
|
|
240
305
|
// SSEClientTransport is deprecated in favour of Streamable HTTP, but both concrete
|
|
@@ -249,22 +314,23 @@ export type MakeTransport = (authProvider?: OAuthClientProvider) => HttpFamilyTr
|
|
|
249
314
|
* demands one. Stored tokens ride the first attempt so the SDK refreshes
|
|
250
315
|
* silently; a 401 without tokens asks the user, opens the browser, catches the
|
|
251
316
|
* loopback redirect, and exchanges the code via the SDK's finishAuth.
|
|
252
|
-
*
|
|
253
|
-
*
|
|
317
|
+
* A server with configured authentication (a bearer token, a static Authorization
|
|
318
|
+
* header, or one from a headersHelper) never enters the OAuth path: the credential
|
|
319
|
+
* to fix is the configured one, so an auth failure reports as a failed connection.
|
|
254
320
|
*/
|
|
255
|
-
async function connectHttpFamily(name: string, config: { url: string }, makeTransport: MakeTransport, label: string,
|
|
256
|
-
const newClient = () =>
|
|
321
|
+
async function connectHttpFamily(name: string, config: { url: string }, makeTransport: MakeTransport, label: string, hasConfiguredAuth: boolean, authUi: AuthUi | undefined, session?: SessionDirs): Promise<Client> {
|
|
322
|
+
const newClient = () => makeClient(session)
|
|
257
323
|
// Stored tokens ride the first attempt so the SDK refreshes them; with none, no
|
|
258
324
|
// provider is attached, so a 401 surfaces as a transport error carrying code 401
|
|
259
325
|
// (isUnauthorized detects it) and only the interactive provider below ever runs
|
|
260
326
|
// dynamic registration, keeping it bound to the real callback port.
|
|
261
|
-
const silent =
|
|
327
|
+
const silent = hasConfiguredAuth ? undefined : new FileOAuthProvider(name, () => {})
|
|
262
328
|
try {
|
|
263
329
|
const client = newClient()
|
|
264
330
|
await connectWithTimeout(client, makeTransport(silent?.hasTokens() ? silent : undefined), label)
|
|
265
331
|
return client
|
|
266
332
|
} catch (error) {
|
|
267
|
-
if (
|
|
333
|
+
if (hasConfiguredAuth || !isUnauthorized(error)) throw error
|
|
268
334
|
if (!authUi) throw new OAuthRequiredError(`${name} requires a login; run pi interactively to authenticate`)
|
|
269
335
|
return await serializeInteractiveOAuth(() => runInteractiveOAuth(name, config, makeTransport, label, authUi, newClient))
|
|
270
336
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-code",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.27",
|
|
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",
|