pi-profile-switch 0.13.0 → 0.13.2

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/README.md CHANGED
@@ -10,15 +10,15 @@ Named profiles for [Pi](https://github.com/badlogic/pi-mono). A profile is a nam
10
10
  npm install -g pi-profile-switch
11
11
  ```
12
12
 
13
- Requires [Pi](https://www.npmjs.com/package/@earendil-works/pi-coding-agent) (installed automatically as a peer dependency).
13
+ Requires [Pi](https://www.npmjs.com/package/@earendil-works/pi-coding-agent) 0.99.1 or newer (installed automatically as a peer dependency). Use `npm install -g`, not `pi install` — this package provides the `pi-profile` launcher.
14
14
 
15
15
  ## Quick start
16
16
 
17
17
  ```bash
18
- # Launch with the built-in default profile (all resources, plain Pi behavior)
18
+ # Built-in default profile: all resources, plain Pi behavior
19
19
  pi-profile
20
20
 
21
- # Launch with the seeded read-only ask profile
21
+ # Starter read-only ask profile
22
22
  pi-profile ask
23
23
 
24
24
  # Anything after -- is passed to pi verbatim
@@ -27,58 +27,28 @@ pi-profile ask -- --model openai/gpt-5.4
27
27
 
28
28
  ## Define your own profiles
29
29
 
30
- Profiles live in two directories, with one JSON file per profile:
30
+ Profiles live in two directories, one JSON file per profile:
31
31
 
32
32
  | Path | Scope |
33
33
  | --- | --- |
34
- | `~/.pi-profile-switch/profiles/<name>.json` | Global, all projects. `PI_PROFILE_SWITCH_DIR` overrides the workspace root. |
35
- | `<project>/.pi/profiles/<name>.json` | Project-level, trusted projects only. |
34
+ | `~/.pi-profile-switch/profiles/<name>.json` | Global, all projects. `PI_PROFILE_SWITCH_DIR` overrides the root directory. |
35
+ | `<project>/.pi/profiles/<name>.json` | Project-level, trusted projects only. Completely replaces a global profile with the same name. |
36
36
 
37
- Create or change a profile by editing or creating a `<name>.json` file directly — schema: [`schemas/profiles.schema.json`](schemas/profiles.schema.json).
37
+ Write the JSON directly (schema: [`schemas/profiles.schema.json`](schemas/profiles.schema.json)), or configure profiles conversationally: the package ships a [`profile-config`](skills/profile-config/SKILL.md) skill that creates, edits, and deletes profiles. Profiles created with a `skills` list include `"profile-config"` by default (unless you opt out or cover it with a wildcard like `"*"`), keeping configuration available after switching.
38
38
 
39
- You can also configure profiles conversationally: the package ships a **`profile-config`** skill (distributed to `<agentDir>/skills/profile-config/` — best-effort on install, and guaranteed in place at every launcher startup) that guides the agent to clarify requirements, discover resources, and write or remove profile files. Profiles created with a `skills` list include `"profile-config"` by default (unless explicitly opted out or covered by a wildcard like `"*"`), keeping configuration available after switching. Details: [`skills/profile-config/SKILL.md`](skills/profile-config/SKILL.md).
39
+ When the global profiles directory has no profile yet, pi-profile-switch writes a starter **`ask`** profile — read-only Q&A and code exploration. It assumes nothing about your setup; edit or delete it freely. See [`examples/ask.json`](examples/ask.json).
40
40
 
41
- pi-profile-switch seeds the global `profiles/` directory with a starter **`ask`** profile (`ask.json`) — best-effort on install, and guaranteed in place at every launcher startup — read-only Q&A and code exploration. It assumes nothing about your setup; edit or delete it freely:
42
-
43
- ```json
44
- {
45
- "label": "Ask & Discuss",
46
- "description": "Read-only Q&A and code exploration; no file modifications or command execution",
47
- "skills": [],
48
- "extensions": [],
49
- "tools": ["read", "grep", "find", "ls"],
50
- "instructions": "You are in read-only discussion mode. Answer questions and explain code without modifying any files or running shell commands."
51
- }
52
- ```
53
-
54
- One profile can use every field at once. This example `impl` profile (`impl.json`) loads the TDD skill and your internal skills; wires up two MCP servers; allows the built-in tools; restricts GitHub MCP tools while denying Linear tools; and pins the model and standing instructions:
41
+ A profile that uses every field:
55
42
 
56
43
  ```json
57
44
  {
58
45
  "label": "Implementation",
59
46
  "description": "Full-powered implementation profile: every available field, pinned model",
60
- "skills": [
61
- "tdd",
62
- "internal-*"
63
- ],
64
- "mcps": [
65
- "github",
66
- "linear"
67
- ],
68
- "tools": [
69
- "read",
70
- "grep",
71
- "find",
72
- "ls",
73
- "bash",
74
- "edit",
75
- "write"
76
- ],
47
+ "skills": ["tdd", "internal-*"],
48
+ "mcps": ["github", "linear"],
49
+ "tools": ["read", "grep", "find", "ls", "bash", "edit", "write"],
77
50
  "mcp_tools": {
78
- "github": [
79
- "search",
80
- "get_issue"
81
- ],
51
+ "github": ["search", "get_issue"],
82
52
  "linear": []
83
53
  },
84
54
  "defaultProvider": "anthropic",
@@ -88,42 +58,38 @@ One profile can use every field at once. This example `impl` profile (`impl.json
88
58
  }
89
59
  ```
90
60
 
91
- How fields resolve:
61
+ How the fields behave:
92
62
 
93
- - `skills`, `extensions`, `mcps`, `tools` take names or globs (e.g. `"internal-*"`) referencing resources you already installed or configured — profiles never copy them. Installed packages and files in standard locations are discovered automatically; no registration needed.
94
- - `tools` expands strictly against Pi's non-MCP tool registry — built-ins and extension-contributed tools, attributed by registration ownership (`sourceInfo`). Available MCP tools remain usable independently of `tools`.
95
- - `mcp_tools` defines per-server MCP tool filtering: keys are literal configured server names and values are literal MCP tool names as exposed by Pi's built-in MCP extension. Globs are not accepted. An omitted server keeps native access to all its tools; a nonempty array allows only matched tools; an empty array (`[]`) denies all tools for that server while leaving it enabled. Unmatched selectors remain restrictive and are not diagnosed, so confirm selectors with the server before writing them.
96
- - **Migration notes:**
97
- - Former MCP references in `tools` (e.g. `mcp__*`, `<server>_*`) no longer govern MCP access. Move desired MCP tool restrictions to `mcp_tools`.
98
- - Prefixed selectors (e.g. `<server>_<tool>` forms used by previous MCP integrations) no longer apply; replace them with the literal tool names from Pi's built-in MCP extension.
99
- - `mcps` references servers from your Pi user-level MCP configuration (`~/.config/mcp/mcp.json`, `~/.agents/mcp.json`, `~/.agents/mcp/mcp.json`, and `<agentDir>/mcp.json`); connection details stay in those files.
100
- - **Omitting `mcps`** leaves all discovered user-level servers at their normal availability.
101
- - **`mcps: []`** disables every discovered user-level server, including agentDir-only servers; every unselected user-level server is explicitly marked `enabled: false` in the generated instance `mcp.json`. Project-level servers are never narrowed.
102
- - Trusted project-level MCP servers are always kept enabled and are never narrowed by `mcps`.
103
- - Servers using `type: "sse"` cannot be selected; migrate them to streamable HTTP before referencing them in a profile.
104
- - The instance `mcp.json` is always a generated snapshot of the merged user-level configuration. In-session `pi mcp add` edits the instance copy, and the next `/profile use` or `/profile reload` overwrites it with the profile's snapshot.
105
- - Any field you omit keeps plain Pi behavior.
63
+ - `label` and `description` are display metadata. `defaultProvider` and `defaultModel` (declared together) set the startup model; `defaultThinkingLevel` sets its thinking level; `instructions` is appended to the system prompt.
64
+ - `skills`, `extensions`, `mcps`, and `tools` reference installed resources by name or glob; profiles never copy resources. `tools` covers non-MCP tools only (built-ins and extension tools).
65
+ - `mcp_tools` selects tools inside MCP servers by literal server and tool name — globs are rejected. Omit a server to leave it unchanged, use `[]` to deny all of its tools while keeping the server enabled, or list names to allow only those. A literal selector that matches nothing stays restrictive without warning; a server that is unknown, disabled, or project-only fails activation with candidates.
66
+ - `mcps` names user-level servers from `~/.config/mcp/mcp.json`, `~/.agents/mcp.json`, `~/.agents/mcp/mcp.json`, and `<agentDir>/mcp.json`. Omit it to leave all servers as configured; use `[]` to disable every user-level server. Project-level servers (`.pi/mcp.json`) are read by Pi itself and are never narrowed. Legacy SSE servers cannot be selected.
67
+ - Older profiles expressed MCP tool access through `mcp__*` or `<server>_*` entries in `tools`; use `mcp_tools` instead.
68
+ - Every omitted field keeps plain Pi behavior.
106
69
 
107
- The files in [`examples/`](examples/) mirror the two profiles above: `ask.json` is the seeded starter, `example.json` the full-field demo.
70
+ The instance's `mcp.json` is generated by the launcher. Running `pi mcp add` inside a session only edits that generated copy, and the next profile switch or reload overwrites it — edit your real MCP configuration instead.
108
71
 
109
- ## Commands
72
+ [`examples/`](examples/) contains the full example above and the starter `ask`.
110
73
 
111
- The `/profile` command family manages everything in-session:
74
+ ## Commands
112
75
 
113
76
  | Command | What it does |
114
77
  | --- | --- |
115
- | `/profile` | Interactive profile picker; without interactive UI it prints the profile list instead |
116
- | `/profile use <name>` / `/profile reload` | Switch / reload without restarting (rollback on failure) |
117
- | `/profile status` | Active profile details: resolved resources and paths, stored overlay, MCP server tri-state |
118
- | `/profile overlay disable\|enable skill\|extension\|mcp\|tool <name-or-glob>` | Narrow / un-narrow the active profile for this session only; `disable` entries accept names or globs |
119
- | `/profile overlay clear` | Discard the overlay and reactivate the profile exactly as declared |
78
+ | `/profile` | Show the available profiles and pick one (interactive selector; prints the list outside the TUI). |
79
+ | `/profile use <name>` | Switch profiles now. The session reloads with the new resources; a failed switch rolls back. The choice is remembered for the next launch. |
80
+ | `/profile reload` | Re-read the active profile file after editing it. |
81
+ | `/profile status` | Report the active profile, resolved resources and paths, overlay, MCP server state, and conflicts. |
82
+ | `/profile overlay disable\|enable skill\|extension\|mcp\|tool <name-or-glob>` | Narrow or restore resources for this session only. |
83
+ | `/profile overlay clear` | Drop the overlay and use the profile as written. |
120
84
 
121
- All forms work in every mode, including non-interactive ones (`--mode rpc|print|json`); the bare selector degrades to the profile list where no interactive UI exists. The overlay is a runtime-only narrowing: it is never written to a catalog file and never survives a restart. Tools follow the same disable/enable model as the other resource kinds: a tool `disable` entry narrows the profile's resolved tool references — or the runtime's full available tool set when the profile declares no `tools`.
85
+ All of these work in every mode, including non-interactive ones (`--mode text`, `--mode json`, `--mode rpc`). An overlay lives only in the current runtime: it is never written to your profile files and is gone after a restart. Disabling MCP servers with an overlay is not possible on the built-in `default` profile — it has no server list to narrow.
122
86
 
123
87
  ## Docs
124
88
 
125
- - [Architecture](docs/architecture/overview.md) · [ADRs](docs/adr/) · [Glossary](CONTEXT.md)
126
- - JSON Schemas: [`schemas/`](schemas/)
89
+ - Field reference: [`schemas/profiles.schema.json`](schemas/profiles.schema.json)
90
+ - Architecture and terminology: [`docs/architecture/overview.md`](docs/architecture/overview.md) and [`CONTEXT.md`](CONTEXT.md)
91
+ - Decisions: [`docs/adr/`](docs/adr/)
92
+ - Authoring guide: [`skills/profile-config/SKILL.md`](skills/profile-config/SKILL.md)
127
93
 
128
94
  ## License
129
95
 
package/README.zh-CN.md CHANGED
@@ -10,7 +10,7 @@
10
10
  npm install -g pi-profile-switch
11
11
  ```
12
12
 
13
- 依赖 [Pi](https://www.npmjs.com/package/@earendil-works/pi-coding-agent)(作为 peer dependency 自动安装)。
13
+ 需要 [Pi](https://www.npmjs.com/package/@earendil-works/pi-coding-agent) 0.99.1 或更新版本(作为 peer dependency 自动安装)。请用 `npm install -g` 安装,而不是 `pi install`:本包提供的是 `pi-profile` 启动器。
14
14
 
15
15
  ## 快速上手
16
16
 
@@ -18,7 +18,7 @@ npm install -g pi-profile-switch
18
18
  # 使用内建 default profile 启动(全量资源,等同原生 Pi)
19
19
  pi-profile
20
20
 
21
- # 使用 starter 只读 ask profile 启动
21
+ # 使用初始的只读 ask profile 启动
22
22
  pi-profile ask
23
23
 
24
24
  # -- 后面的参数原样传给 pi
@@ -27,62 +27,28 @@ pi-profile ask -- --model openai/gpt-5.4
27
27
 
28
28
  ## 自定义 profile
29
29
 
30
- profile 保存在两个目录中,每个 profile 对应一个独立 JSON 文件:
30
+ profile 保存在两个目录中,每个 profile 对应一个 JSON 文件:
31
31
 
32
32
  | 路径 | 作用域 |
33
33
  | --- | --- |
34
34
  | `~/.pi-profile-switch/profiles/<name>.json` | 全局,对所有项目生效。`PI_PROFILE_SWITCH_DIR` 可自定义根目录。 |
35
- | `<项目>/.pi/profiles/<name>.json` | 项目级,仅对已信任项目生效。 |
35
+ | `<项目>/.pi/profiles/<name>.json` | 项目级,仅对已信任项目生效;同名时会完全替换全局 profile。 |
36
36
 
37
- 直接编辑或新建 `<name>.json` 即可创建或修改 profile——schema 见 [`schemas/profiles.schema.json`](schemas/profiles.schema.json)。
37
+ 直接写 JSON 文件即可(schema 见 [`schemas/profiles.schema.json`](schemas/profiles.schema.json)),也可以让 agent 帮你改:本包自带 [`profile-config`](skills/profile-config/SKILL.md) skill,能创建、修改和删除 profile。只要新建的 profile 声明了 `skills`,默认会包含 `"profile-config"`(除非你明确排除,或已被 `"*"` 之类的 glob 覆盖),这样切过去之后还能继续用对话调整。
38
38
 
39
- 你也可以通过对话让 agent 帮你配置:随包附带的 **`profile-config`** skill(安装时 best-effort 分发、launcher 启动时保证就位,位于 `<agentDir>/skills/profile-config/`)会指导 agent 澄清需求、发现资源并读写 profile 文件。按约定,生成声明了 `skills` 的 profile 时默认包含 `"profile-config"`(除非明确排除或已被 `*` 等 glob 覆盖),确保切换到新 profile 后仍可持续对话配置。详情见 [`skills/profile-config/SKILL.md`](skills/profile-config/SKILL.md)。
39
+ 全局 `profiles/` 目录里还没有 profile 时,pi-profile-switch 会写入一个初始 **`ask`** profile——只读问答与代码走读。它不假设你安装过任何插件,可随意修改或删除。见 [`examples/ask.json`](examples/ask.json)。
40
40
 
41
- pi-profile-switch 会向全局 `profiles/` 目录播种一个初始 **`ask`** profile(`ask.json`)——安装时 best-effort、launcher 启动时保证就位——它是只读的问答与代码走读模式。它不假设你安装过任何插件,可随意修改或删除:
42
-
43
- ```json
44
- {
45
- "label": "Ask & Discuss",
46
- "description": "Read-only Q&A and code exploration; no file modifications or command execution",
47
- "skills": [],
48
- "extensions": [],
49
- "tools": ["read", "grep", "find", "ls"],
50
- "instructions": "You are in read-only discussion mode. Answer questions and explain code without modifying any files or running shell commands."
51
- }
52
- ```
53
-
54
- 一个 profile 可以同时使用全部字段。下面这个 `impl` profile 示例(`impl.json`)加载 TDD skill、mcp-scripting skill(pi-mcp-adapter 自带)和你的内部 skills;接入两个 MCP server;允许内建工具并按 server 细化 MCP 工具;同时钉住模型与常驻 instructions:
41
+ 一个用到全部字段的 profile:
55
42
 
56
43
  ```json
57
44
  {
58
45
  "label": "Implementation",
59
46
  "description": "Full-powered implementation profile: every available field, pinned model",
60
- "skills": [
61
- "tdd",
62
- "internal-*",
63
- "mcp-scripting"
64
- ],
65
- "extensions": [
66
- "pi-mcp-adapter"
67
- ],
68
- "mcps": [
69
- "github",
70
- "linear"
71
- ],
72
- "tools": [
73
- "read",
74
- "grep",
75
- "find",
76
- "ls",
77
- "bash",
78
- "edit",
79
- "write"
80
- ],
47
+ "skills": ["tdd", "internal-*"],
48
+ "mcps": ["github", "linear"],
49
+ "tools": ["read", "grep", "find", "ls", "bash", "edit", "write"],
81
50
  "mcp_tools": {
82
- "github": [
83
- "search",
84
- "get_issue"
85
- ],
51
+ "github": ["search", "get_issue"],
86
52
  "linear": []
87
53
  },
88
54
  "defaultProvider": "anthropic",
@@ -92,41 +58,39 @@ pi-profile-switch 会向全局 `profiles/` 目录播种一个初始 **`ask`** pr
92
58
  }
93
59
  ```
94
60
 
95
- 字段解析规则:
61
+ 字段行为:
96
62
 
97
- - `skills`、`extensions`、`mcps`、`tools` 接受名称或 glob(如 `"internal-*"`),引用你已安装或已配置的资源——profile 从不复制资源。已安装的包和标准目录下的文件会被自动发现,无需注册。
98
- - `tools` 仅针对 Pi 的非 MCP 工具展开(内建工具与 extension 提供的工具,根据注册归属 `sourceInfo` 判定)。可用的 MCP 工具独立于 `tools` 保持可用。
99
- - `mcp_tools` 按 server 细化 MCP 工具策略:键为字面 MCP server 名称,值为 pi-mcp-adapter 的字面 selector(原始名或带前缀名;两种写法都可能选中同一个工具)。此字段不接受 glob。省略的 server 保留其全部原生工具访问;非空数组仅允许匹配到的工具;空数组(`[]`)禁用该 server 的全部工具,同时保持 server 处于启用状态。未匹配的 selector 仍保持限制且不会收到诊断,因此编写前请通过 adapter/server 确认可用写法。
100
- - 非空的 `mcp_tools` 即使在省略 `mcps` 时也需要 `pi-mcp-adapter`。只有当 profile selector 与已有的 `includeTools` 字面值完全相同,或已有列表是 `"*"` 时才能安全组合;否则会在写入 runtime 文件前报错。已有的 `excludeTools` 限制仍然生效。
101
- - **迁移提示**:旧 profile 中写入 `tools` 的 MCP 工具名称或 glob(如 `mcp__*`、`<server>_*`)不再控制 MCP 访问;如有需要请迁移至 `mcp_tools`。
102
- - `mcps` 引用 pi-mcp-adapter 配置中的 server;连接细节留在 adapter 自己的配置里。
103
- - **省略 `mcps`**:保留所有已发现用户级 server 的原生可用性。
104
- - **`mcps: []` 且选中了 `pi-mcp-adapter`**:禁用所有已发现用户级 server(共享位置的 server 会被显式标记为 `disabled`;仅位于 agentDir 的 server 会从生成的实例 `mcp.json` 中省略)。
105
- - **`mcps: []` 但未选中 `pi-mcp-adapter`**:该声明不生效,不会读取 MCP 配置、改变 server 可用性,也不会因配置文件损坏而报错。
106
- - 受信任的项目级 MCP server 始终保持启用,不受 `mcps` 收窄。
107
- - 未写的字段保持原生 Pi 行为。
63
+ - `label`、`description` 仅用于显示。`defaultProvider` 与 `defaultModel` 需同时声明,用于指定启动模型;`defaultThinkingLevel` 指定思考等级;`instructions` 会追加到系统提示词。
64
+ - `skills`、`extensions`、`mcps`、`tools` 以名称或 glob 引用已安装的资源;profile 不会复制资源。`tools` 只涉及非 MCP 工具(内置工具和扩展工具)。
65
+ - `mcp_tools` 按字面 server 名和工具名选择 MCP 工具,不接受 glob。省略某个 server 表示保持原样,写 `[]` 表示禁用它的全部工具但保留 server 本身,列出名称表示只允许这些工具。字面选择器匹配不到内容时仍保持限制且不报错;server 未知、已禁用或只存在于项目级配置时,激活会失败并给出候选。
66
+ - `mcps` 列出用户级 server,可来自 `~/.config/mcp/mcp.json`、`~/.agents/mcp.json`、`~/.agents/mcp/mcp.json` 和 `<agentDir>/mcp.json`。省略表示全部保持原样;写 `[]` 表示禁用所有用户级 server。项目级 server(`.pi/mcp.json`)由 Pi 自己读取,profile 不会收窄。旧的 SSE server 无法选择。
67
+ - 旧版 profile 用 `tools` 里的 `mcp__*`、`<server>_*` 表达 MCP 工具访问;现在请改用 `mcp_tools`。
68
+ - 未声明的字段完全保持原生 Pi 行为。
108
69
 
109
- [`examples/`](examples/) 中的两个文件与上面一一对应:`ask.json` 是播种的初始 profile,`example.json` 是全字段演示。
70
+ 实例目录里的 `mcp.json` 由启动器生成。在会话里执行 `pi mcp add` 只会改到这份生成副本,下次切换 profile 或重载时会被覆盖——要永久生效,请直接修改你自己的 MCP 配置。
110
71
 
111
- ## 命令
72
+ [`examples/`](examples/) 里有上面的完整示例和初始 `ask`。
112
73
 
113
- `/profile` 命令族完成所有会话内操作:
74
+ ## 命令
114
75
 
115
76
  | 命令 | 作用 |
116
77
  | --- | --- |
117
- | `/profile` | 交互式选择 profile;无交互界面时打印 profile 列表 |
118
- | `/profile use <name>` / `/profile reload` | 会话内切换 / 重载(失败自动回滚) |
119
- | `/profile status` | 查看活动 profile 详情:解析后的资源与路径、已存 overlay、MCP server 三态 |
120
- | `/profile overlay disable\|enable skill\|extension\|mcp\|tool <name-or-glob>` | 仅本次会话收窄 / 恢复活动 profile;`disable` 接受名称或 glob |
121
- | `/profile overlay clear` | 丢弃 overlay,完全按定义重新激活 profile |
78
+ | `/profile` | 显示可选 profile 并选择(TUI 里是交互式选择器,非 TUI 下打印列表)。 |
79
+ | `/profile use <name>` | 立即切换 profile。会话会用新资源重载;切换失败会回滚。选择会被记住,下次启动继续使用。 |
80
+ | `/profile reload` | 修改 profile 文件后重新读取。 |
81
+ | `/profile status` | 显示当前 profile、解析出的资源与路径、overlay、MCP server 状态以及冲突。 |
82
+ | `/profile overlay disable\|enable skill\|extension\|mcp\|tool <name-or-glob>` | 只对当前会话收窄或恢复资源。 |
83
+ | `/profile overlay clear` | 去掉 overlay,按 profile 本来的写法运行。 |
122
84
 
123
- 所有命令在非交互模式(`--mode rpc|print|json`)下同样生效;裸 `/profile` 在无交互界面时降级为 profile 列表。overlay 是会话级收窄:绝不写入 catalog 文件,也不会跨重启保留。tool 与其他资源类别使用相同的 disable/enable 模型:tool `disable` 条目收窄 profile 解析出的工具引用;profile 未声明 `tools` 时,则收窄运行时的全部可用工具集。
85
+ 以上命令在所有模式下都可用,包括非交互模式(`--mode text`、`--mode json`、`--mode rpc`)。overlay 只作用于当前运行期:不会写入 profile 文件,重启后即消失。内建的 `default` profile 不能用 overlay 禁用 MCP server——它没有可供收窄的 MCP 白名单。
124
86
 
125
87
  ## 文档
126
88
 
127
- - [架构设计](docs/architecture/overview.md) · [ADR](docs/adr/) · [术语表](CONTEXT.md)
128
- - JSON Schema:[`schemas/`](schemas/)
89
+ - 字段参考:[`schemas/profiles.schema.json`](schemas/profiles.schema.json)
90
+ - 架构与术语:[`docs/architecture/overview.md`](docs/architecture/overview.md)、[`CONTEXT.md`](CONTEXT.md)
91
+ - 设计决策:[`docs/adr/`](docs/adr/)
92
+ - Profile 编写指南:[`skills/profile-config/SKILL.md`](skills/profile-config/SKILL.md)
129
93
 
130
- ## 许可证
94
+ ## License
131
95
 
132
96
  MIT
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-profile-switch",
3
- "version": "0.13.0",
3
+ "version": "0.13.2",
4
4
  "description": "Named profiles for Pi: reference skills, extensions, MCP servers, and tools per workflow, switched without restarting. Install: npm install -g pi-profile-switch (not pi install).",
5
5
  "type": "module",
6
6
  "keywords": [
@@ -30,10 +30,10 @@
30
30
  "test:watch": "vitest"
31
31
  },
32
32
  "peerDependencies": {
33
- "@earendil-works/pi-coding-agent": "*"
33
+ "@earendil-works/pi-coding-agent": ">=0.99.1"
34
34
  },
35
35
  "devDependencies": {
36
- "@earendil-works/pi-coding-agent": "*",
36
+ "@earendil-works/pi-coding-agent": ">=0.99.1",
37
37
  "@types/node": "^24.0.0",
38
38
  "ajv": "^8.20.0",
39
39
  "typescript": "^5.8.0",
@@ -170,6 +170,29 @@ function homeAgentsSkillsDir(): string {
170
170
  return path.join(process.env.HOME ?? homedir(), ".agents", "skills");
171
171
  }
172
172
 
173
+ /** The user's own skill exclusions (`!pattern`, `-path`), carried into a
174
+ * named profile's generated settings. Relative entries resolve against the
175
+ * runtime dir, which mirrors the agent dir, so they pass through unchanged.
176
+ * An absolute (or `~`) `-path` under the agent dir is rewritten to its
177
+ * runtime mirror path: Pi matches `-` entries lexically. */
178
+ function userSkillExclusions(userSkills: unknown, agentDir: string, runtimeDir: string): string[] {
179
+ if (!Array.isArray(userSkills)) return [];
180
+ const exclusions: string[] = [];
181
+ for (const entry of userSkills) {
182
+ if (typeof entry !== "string") continue;
183
+ if (entry.startsWith("!")) {
184
+ exclusions.push(entry);
185
+ } else if (entry.startsWith("-")) {
186
+ const target = entry.slice(1);
187
+ const expanded = target === "~" || target.startsWith("~/") ? path.join(process.env.HOME ?? homedir(), target.slice(1)) : target;
188
+ const rel = path.isAbsolute(expanded) ? path.relative(agentDir, expanded) : undefined;
189
+ const underAgentDir = rel !== undefined && rel !== "" && !rel.startsWith("..") && !path.isAbsolute(rel);
190
+ exclusions.push(underAgentDir ? `-${path.join(runtimeDir, rel)}` : entry);
191
+ }
192
+ }
193
+ return exclusions;
194
+ }
195
+
173
196
  function buildSelectionSettings(
174
197
  plan: ActivationPlan,
175
198
  userSettings: Record<string, unknown>,
@@ -213,6 +236,11 @@ function buildSelectionSettings(
213
236
  skillEntries.push(`-${skill.filePath}`);
214
237
  }
215
238
  }
239
+ // Discovery already honored the user's own `!pattern` / `-path` skill
240
+ // exclusions, so the skills they hide never reach the loop above and get no
241
+ // `-` entry. Replacing the user's array would then drop those exclusions and
242
+ // let the instance's skills symlink and ~/.agents/skills reveal them.
243
+ skillEntries.push(...userSkillExclusions(userSettings.skills, agentDir, runtimeDir));
216
244
  settings.skills = skillEntries;
217
245
 
218
246
  // --- extensions ---
@@ -4,7 +4,7 @@
4
4
  * Two independent sources, checked once per Pi process launch and presented
5
5
  * through a caller-supplied NoticeSurface:
6
6
  *
7
- * - npm package metadata: compare the running package version against the
7
+ * - npm dist-tags: compare the running package version against the
8
8
  * registry's installable `latest` dist-tag and remind once per target
9
9
  * version (design decision 2: authority and cache).
10
10
  * - a single maintainer-reviewed `announcements.json` feed (design decision
@@ -30,9 +30,9 @@ import { isRecord } from "./json-file.ts";
30
30
  export const ANNOUNCEMENTS_URL =
31
31
  "https://raw.githubusercontent.com/VincentFF/pi-profile-switch/main/announcements.json";
32
32
 
33
- /** npm registry package metadata; the installable stable version is read
34
- * from `dist-tags.latest`, never from the largest published version. */
35
- const NPM_METADATA_URL = "https://registry.npmjs.org/pi-profile-switch";
33
+ /** npm registry dist-tags; the installable stable version is read from
34
+ * top-level `latest`, never from the largest published version. */
35
+ const NPM_DIST_TAGS_URL = "https://registry.npmjs.org/-/package/pi-profile-switch/dist-tags";
36
36
 
37
37
  /** Hard ceiling on one remote check; the timer is unref'd so a pending
38
38
  * check never keeps a short-lived Pi process alive. */
@@ -261,10 +261,10 @@ function parseNpmLatest(text: string): string {
261
261
  } catch {
262
262
  throw new InvalidContentError("response is not valid JSON");
263
263
  }
264
- if (!isRecord(raw) || !isRecord(raw["dist-tags"]) || typeof raw["dist-tags"].latest !== "string") {
265
- throw new InvalidContentError("missing dist-tags.latest");
264
+ if (!isRecord(raw) || typeof raw.latest !== "string") {
265
+ throw new InvalidContentError("missing latest");
266
266
  }
267
- const latest = raw["dist-tags"].latest;
267
+ const latest = raw.latest;
268
268
  if (parseVersion(latest) === undefined) {
269
269
  throw new InvalidContentError(`malformed latest version: ${JSON.stringify(latest)}`);
270
270
  }
@@ -574,7 +574,7 @@ async function run(options: StartupNotifierOptions): Promise<void> {
574
574
  const refreshedNpm = await refreshSource<{ latest: string }>({
575
575
  dir,
576
576
  name: "npm-latest",
577
- url: NPM_METADATA_URL,
577
+ url: NPM_DIST_TAGS_URL,
578
578
  label: "npm registry",
579
579
  cache: npmCache,
580
580
  parse: (text) => ({ latest: parseNpmLatest(text) }),