pi-profile-switch 0.3.0 → 0.4.0

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.
Files changed (53) hide show
  1. package/README.md +32 -32
  2. package/README.zh-CN.md +32 -32
  3. package/bin/pi-profile.js +11 -0
  4. package/bin/pi-profile.ts +65 -0
  5. package/bin/postinstall.d.ts +13 -0
  6. package/bin/postinstall.js +88 -0
  7. package/defaults/profiles.json +18 -0
  8. package/examples/profiles.json +31 -5
  9. package/extensions/pi-profile/index.ts +451 -0
  10. package/package.json +10 -17
  11. package/schemas/profiles.schema.json +22 -24
  12. package/src/extension-discovery.ts +347 -0
  13. package/src/json-file.ts +1 -21
  14. package/src/launcher/args.ts +57 -0
  15. package/src/launcher/discovery.ts +64 -0
  16. package/src/launcher/initial-profile.ts +179 -0
  17. package/src/launcher/model-check.ts +52 -0
  18. package/src/launcher/runtime-cleanup.ts +85 -0
  19. package/src/launcher/spawn.ts +82 -0
  20. package/src/mcp-config.ts +37 -153
  21. package/src/mcp-coordination.ts +29 -10
  22. package/src/profile-catalog-store.ts +31 -12
  23. package/src/profile-catalog.ts +44 -63
  24. package/src/profile-resolver.ts +239 -245
  25. package/src/project-trust.ts +82 -0
  26. package/src/runtime-state-store.ts +25 -35
  27. package/src/settings-generator.ts +541 -0
  28. package/src/skill-registry.ts +94 -0
  29. package/src/switching/apply-plan.ts +197 -0
  30. package/src/switching/customize.ts +62 -33
  31. package/src/switching/list-profiles.ts +9 -6
  32. package/src/switching/mcp-toggle.ts +21 -25
  33. package/src/switching/profile-crud.ts +31 -24
  34. package/src/switching/profile-wizard.ts +21 -49
  35. package/src/switching/status.ts +142 -72
  36. package/src/switching/switch-profile.ts +219 -0
  37. package/src/switching/tool-references.ts +40 -0
  38. package/src/workspace.ts +57 -0
  39. package/LICENSE +0 -21
  40. package/examples/profiles.example.json +0 -74
  41. package/extensions/pi-profile-switch/index.ts +0 -744
  42. package/src/adapter-presence.ts +0 -75
  43. package/src/mcp-overlay-file.ts +0 -35
  44. package/src/mcp-overlay.ts +0 -122
  45. package/src/model-selection.ts +0 -64
  46. package/src/name-matching.ts +0 -50
  47. package/src/profile-badge.ts +0 -142
  48. package/src/profile-presets.ts +0 -61
  49. package/src/skill-selection.ts +0 -81
  50. package/src/startup-mcp-scope.ts +0 -262
  51. package/src/startup-selection.ts +0 -144
  52. package/src/switching/activate-profile.ts +0 -115
  53. package/src/switching/apply-profile.ts +0 -131
package/README.md CHANGED
@@ -2,16 +2,14 @@
2
2
 
3
3
  [English](README.md) | [中文](README.zh-CN.md)
4
4
 
5
- Named profiles for [Pi](https://github.com/badlogic/pi-mono). A profile selects what the model sees and which capabilities the session uses — skills, MCP servers, tools, a model preset, and extra instructions — switchable in the same Pi process.
5
+ Named profiles for [Pi](https://github.com/badlogic/pi-mono). A profile references a set of existing skills, extensions, MCP servers, and tools switch between them in the same Pi process, without restarting.
6
6
 
7
- A shipped `read-only` preset covers the read-and-report workflow; other workflows you write yourself — a profile can narrow the tool set, hide skills, pin a model, or add instructions.
8
-
9
- `pi-profile-switch` is a plain Pi package (ADR-0007). It installs like any other extension, leaves Pi's configuration directory untouched, and keeps sessions, packages, project trust, and every other installed extension native.
7
+ Use a lean read-only profile for code review, a full-powered one for implementation, a minimal one for a quick question all against the same installed resources.
10
8
 
11
9
  ## Install
12
10
 
13
11
  ```bash
14
- pi install npm:pi-profile-switch
12
+ npm install -g pi-profile-switch
15
13
  ```
16
14
 
17
15
  Requires [Pi](https://www.npmjs.com/package/@earendil-works/pi-coding-agent) (installed automatically as a peer dependency).
@@ -19,33 +17,36 @@ Requires [Pi](https://www.npmjs.com/package/@earendil-works/pi-coding-agent) (in
19
17
  ## Quick start
20
18
 
21
19
  ```bash
22
- # Use the saved profile (or the built-in default)
23
- pi
20
+ # Launch with the built-in default profile (all resources, plain Pi behavior)
21
+ pi-profile
24
22
 
25
- # Use a profile for this run only (not saved)
26
- pi --profile read-only
23
+ # Launch with a named profile
24
+ pi-profile review
27
25
 
28
- # The explicit native baseline
29
- pi --profile default
26
+ # Anything after -- is passed to pi verbatim
27
+ pi-profile review -- --model openai/gpt-5.4
30
28
  ```
31
29
 
32
- Create profiles with `/profile create`, which writes `~/.pi/agent/profiles.json` (global) or `<project>/.pi/profiles.json` (project, trusted projects only). The wizard offers the shipped `read-only` preset — Pi's built-in tools only, no skills, MCP servers, or model assumed — or a blank definition.
33
-
34
- A preset is a one-time copy into your catalog: it is not tracked, so a package update never changes a profile you already created. Profiles **reference** resources by name — they never copy them. [`examples/profiles.example.json`](examples/profiles.example.json) is a complete catalog with several profiles covering every field; replace its resource names with ones you own.
35
-
36
- `schemaVersion` is 1 and is the only accepted value. A profile cannot select extensions: they load natively in every profile, so manage them with `pi install`.
37
-
38
- ## What a profile controls
30
+ Define profiles in `~/.pi-profile-switch/profiles.json` (global, fallback to `~/.pi/agent/profiles.json` for migration; custom root via `PI_PROFILE_SWITCH_DIR`) or `<project>/.pi/profiles.json` (project, trusted projects only):
31
+
32
+ ```json
33
+ {
34
+ "schemaVersion": 1,
35
+ "profiles": {
36
+ "review": {
37
+ "label": "Code review",
38
+ "skills": ["code-review"],
39
+ "mcp": ["github"],
40
+ "tools": ["read", "grep", "find", "bash"],
41
+ "instructions": "Review only; do not modify files."
42
+ }
43
+ }
44
+ }
45
+ ```
39
46
 
40
- | Field | Effect |
41
- | --- | --- |
42
- | `instructions` | Text appended to the system prompt every turn |
43
- | `model` | Session-start model preset; an explicit `--model`/`--thinking` or a model recorded in the session wins |
44
- | `skills` | What the model sees in the prompt's skills list. Every installed skill stays loaded and callable by the user through `/skill:name` |
45
- | `mcp` | Which MCP servers the session exposes: the profile's allowlist is written into the adapter's own config (`~/.pi/agent/mcp.json`), disabling every other server. Servers you keep in that file move to `mcp.user.json`; connection details stay in your config, never in the profile |
46
- | `tools` | Active tool set: declared names/globs become the active set; names that register later (MCP, extensions) are applied when they appear |
47
+ Profiles **reference** resources by name — they never copy them. Installed packages and files in standard locations are discovered automatically; no registration needed. Full schema with more examples: [`examples/profiles.json`](examples/profiles.json).
47
48
 
48
- Anything a profile does not declare keeps Pi's native behavior, and `default` declares nothing.
49
+ On install, pi-profile-switch seeds `~/.pi-profile-switch/profiles.json` with a starter **`ask`** profile — read-only Q&A and code exploration (`read`/`grep`/`find`/`ls`, no skills, extensions, or MCP). It assumes nothing about your setup; edit or delete it freely.
49
50
 
50
51
  ## Commands
51
52
 
@@ -55,25 +56,24 @@ In the TUI, the `/profile` command family manages everything in-session:
55
56
  | --- | --- |
56
57
  | `/profile` | Interactive profile picker |
57
58
  | `/profile list` / `/profile status` | Show profiles / active profile details |
58
- | `/profile use <name>` | Switch in place same session; when the MCP selection changes, the runtime reloads automatically to re-apply it |
59
+ | `/profile use <name>` / `/profile reload` | Switch / reload without restarting (rollback on failure) |
59
60
  | `/profile create\|edit\|delete\|duplicate` | Guided profile CRUD (TUI only) |
61
+ | `/profile resource create\|edit\|delete` | Guided resource overrides |
60
62
  | `/profile customize` / `/profile reset` | Narrow the active profile for this session only |
61
63
  | `/mcp enable\|disable <server>` | Toggle MCP servers in the active profile |
62
64
 
63
- While a non-`default` profile is active, the footer shows `profile: <name>`, with `*` appended when a session-only overlay is in effect. `default` shows no badge, so an unprofiled session keeps Pi's native footer.
64
-
65
65
  All commands work in non-interactive modes (`--mode rpc|print|json`); CRUD wizards are TUI-only.
66
66
 
67
67
  ## Guarantees
68
68
 
69
69
  - **Reference, never copy** — profiles point at resources you already own and maintain.
70
- - **Pi-native** — the configuration directory is Pi's own, so sessions, extension config, packages, context files, and trust behave exactly as they do in plain Pi.
71
- - **Fail safe** — untrusted project directories are never read; a failed activation applies nothing and reports the cause.
72
- - **No manual reload** — switching re-applies runtime state in place and the next turn's prompt carries the new selection. Only a change to the MCP selection rebuilds the runtime, automatically, in the same session.
70
+ - **Pi-native** — anything a profile doesn't explicitly control keeps plain Pi behavior.
71
+ - **Fail safe** — untrusted project directories are never read; a failed switch rolls back to the last working configuration.
73
72
 
74
73
  ## Docs
75
74
 
76
75
  - [Architecture](docs/architecture/overview.md) · [ADRs](docs/adr/) · [Glossary](CONTEXT.md) (Chinese)
76
+ - JSON Schemas: [`schemas/`](schemas/)
77
77
 
78
78
  ## License
79
79
 
package/README.zh-CN.md CHANGED
@@ -2,16 +2,14 @@
2
2
 
3
3
  [English](README.md) | [中文](README.zh-CN.md)
4
4
 
5
- [Pi](https://github.com/badlogic/pi-mono) 的命名 profile 扩展。一个 profile 选择模型看到什么、会话使用哪些能力——skills、MCP servertools、预制模型和附加指令——并在同一 Pi 进程内即时切换。
5
+ [Pi](https://github.com/badlogic/pi-mono) 的命名 profile 扩展。一个 profile 引用一组已存在的 skills、extensions、MCP servertools,并在同一 Pi 进程内切换,无需重启。
6
6
 
7
- 随包提供一个 `read-only` 预设覆盖「读代码 + 报告」的工作流;其余工作流由你自己定义——profile 可以收窄工具集、隐藏 skill、固定模型或附加指令。
8
-
9
- `pi-profile-switch` 是普通 Pi package(ADR-0007):像其他扩展一样安装,不改动 Pi 的配置目录,session、packages、项目信任和其他扩展全部保持原生。
7
+ 代码评审用精简的只读 profile,实现需求用全量 profile,临时提问用最小 profile——全部基于同一套已安装的资源。
10
8
 
11
9
  ## 安装
12
10
 
13
11
  ```bash
14
- pi install npm:pi-profile-switch
12
+ npm install -g pi-profile-switch
15
13
  ```
16
14
 
17
15
  依赖 [Pi](https://www.npmjs.com/package/@earendil-works/pi-coding-agent)(作为 peer dependency 自动安装)。
@@ -19,33 +17,36 @@ pi install npm:pi-profile-switch
19
17
  ## 快速上手
20
18
 
21
19
  ```bash
22
- # 使用已保存的 profile(不存在时用内建 default
23
- pi
20
+ # 使用内建 default profile 启动(全量资源,等同原生 Pi
21
+ pi-profile
24
22
 
25
- # 仅本次启动使用指定 profile(不保存)
26
- pi --profile read-only
23
+ # 使用指定 profile 启动
24
+ pi-profile review
27
25
 
28
- # 显式的原生基线
29
- pi --profile default
26
+ # -- 后面的参数原样传给 pi
27
+ pi-profile review -- --model openai/gpt-5.4
30
28
  ```
31
29
 
32
- `/profile create` 创建 profile,写入 `~/.pi/agent/profiles.json`(全局)或 `<项目>/.pi/profiles.json`(项目级,仅限已信任项目)。向导提供随包的 `read-only` 预设——只用 Pi 内建工具,不假设任何 skill、MCP server 或模型——也可以从空定义开始。
33
-
34
- 预设只被复制一次:它不被跟踪,包升级不会改动你已经创建的 profile。Profile 只**引用**资源,从不复制资源。[`examples/profiles.example.json`](examples/profiles.example.json) 是覆盖全部字段的完整 catalog,示例中的资源名请替换成你本机已有的名字。
35
-
36
- `schemaVersion` 1,也是唯一接受的值。Profile 不选择 extension:extensions 在所有 profile 中原生加载,请用 `pi install` 管理。
37
-
38
- ## Profile 控制的范围
30
+ `~/.pi-profile-switch/profiles.json`(全局,支持 `PI_PROFILE_SWITCH_DIR` 环境变量自定义,向下兼容 `~/.pi/agent/profiles.json`)或 `<项目>/.pi/profiles.json`(项目级,仅限已信任项目)中定义 profile:
31
+
32
+ ```json
33
+ {
34
+ "schemaVersion": 1,
35
+ "profiles": {
36
+ "review": {
37
+ "label": "Code review",
38
+ "skills": ["code-review"],
39
+ "mcp": ["github"],
40
+ "tools": ["read", "grep", "find", "bash"],
41
+ "instructions": "Review only; do not modify files."
42
+ }
43
+ }
44
+ }
45
+ ```
39
46
 
40
- | 字段 | 效果 |
41
- | --- | --- |
42
- | `instructions` | 每个 turn 追加到 system prompt 末尾 |
43
- | `model` | 会话启动的模型预设;显式 `--model`/`--thinking` 或 session 历史中记录的模型优先 |
44
- | `skills` | 模型在 prompt skills 列表中看到的内容;所有已安装 skill 仍保持加载,用户可用 `/skill:name` 手动调用 |
45
- | `mcp` | 本次会话暴露哪些 MCP server:白名单写入 adapter 自己的配置(`~/.pi/agent/mcp.json`),其余 server 一律标为 disabled。你原本放在该文件里的 server 会迁移到 `mcp.user.json`;连接参数只在你自己的配置里,从不进入 profile |
46
- | `tools` | 活动工具集:声明的名字/glob 成为活动集合;之后才注册的工具(MCP、扩展)在出现时补上 |
47
+ Profile 只**引用**资源,从不复制资源。已安装的包和标准目录下的文件会被自动发现,无需注册。完整示例见 [`examples/profiles.json`](examples/profiles.json)。
47
48
 
48
- 未声明的字段保持 Pi 原生行为,`default` 什么都不声明。
49
+ 安装时,pi-profile-switch 会向 `~/.pi-profile-switch/profiles.json` 写入一个初始 **`ask`** profile——只读的问答与代码走读模式(仅 `read`/`grep`/`find`/`ls`,无 skill、extension 和 MCP)。它不假设你安装过任何插件,可随意修改或删除。
49
50
 
50
51
  ## 命令
51
52
 
@@ -55,25 +56,24 @@ pi --profile default
55
56
  | --- | --- |
56
57
  | `/profile` | 交互式选择 profile |
57
58
  | `/profile list` / `/profile status` | 列出 profile / 查看活动 profile 详情 |
58
- | `/profile use <name>` | 原位切换——同一 session;当 MCP 选择发生变化时会自动 reload 以重新应用 |
59
+ | `/profile use <name>` / `/profile reload` | 会话内切换 / 重载(失败自动回滚) |
59
60
  | `/profile create\|edit\|delete\|duplicate` | 向导式 profile 增删改(仅 TUI) |
61
+ | `/profile resource create\|edit\|delete` | 向导式资源覆盖管理 |
60
62
  | `/profile customize` / `/profile reset` | 仅本次会话收窄活动 profile |
61
63
  | `/mcp enable\|disable <server>` | 在活动 profile 中开关 MCP server |
62
64
 
63
- 活动 profile 不是 `default` 时,footer 显示 `profile: <name>`;本次会话的 overlay 生效时追加 `*`。`default` 不显示 badge,未使用 profile 的会话保持 Pi 原生 footer。
64
-
65
65
  非交互模式(`--mode rpc|print|json`)下命令同样生效;CRUD 向导仅 TUI 可用。
66
66
 
67
67
  ## 保证
68
68
 
69
69
  - **引用而非复制**——profile 指向你自己拥有和维护的资源。
70
- - **Pi 原生**——配置目录就是 Pi 自己的目录,session、扩展配置、packages、context 文件和信任行为与原生 Pi 完全一致。
71
- - **失败安全**——未信任的项目目录从不读取;激活失败时不应用任何设置并报出原因。
72
- - **无需手动 reload**——切换在原位重新应用运行时状态,下一个 turn 的 prompt 直接带上新选择;只有 MCP 选择变化时才会自动重建运行时(同一 session)
70
+ - **Pi 原生**——profile 未显式控制的一切保持原生 Pi 行为。
71
+ - **失败安全**——未信任的项目目录从不读取;切换失败回滚到上一份可用配置。
73
72
 
74
73
  ## 文档
75
74
 
76
75
  - [架构设计](docs/architecture/overview.md) · [ADR](docs/adr/) · [术语表](CONTEXT.md)
76
+ - JSON Schema:[`schemas/`](schemas/)
77
77
 
78
78
  ## 许可证
79
79
 
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Installed entry point. Node refuses type-stripping under node_modules, so
4
+ * the shipped package loads the TypeScript launcher through jiti (the same
5
+ * loader Pi uses for extensions). Development can run bin/pi-profile.ts
6
+ * directly; both paths share the one TS graph.
7
+ */
8
+ import { createJiti } from "jiti";
9
+
10
+ const jiti = createJiti(import.meta.url);
11
+ await jiti.import("./pi-profile.ts");
@@ -0,0 +1,65 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * pi-profile launcher (ADR-0005).
4
+ *
5
+ * Resolves the initial profile, materializes it as a generated runtime
6
+ * directory (settings + symlinks + env + flags), and spawns the real `pi`
7
+ * binary with user arguments passed through verbatim.
8
+ *
9
+ * Usage:
10
+ * pi-profile # default profile
11
+ * pi-profile review # named profile (once catalogs land)
12
+ * pi-profile review -- --mode rpc --model openai/gpt-5.4
13
+ */
14
+ import { getAgentDir } from "@earendil-works/pi-coding-agent";
15
+
16
+ import { ExtensionError } from "../src/extension-discovery.ts";
17
+ import { parseLauncherArgs } from "../src/launcher/args.ts";
18
+ import { UnknownProfileError, resolveInitialProfile } from "../src/launcher/initial-profile.ts";
19
+ import { sweepStaleRuntimeDirs } from "../src/launcher/runtime-cleanup.ts";
20
+ import { spawnPi } from "../src/launcher/spawn.ts";
21
+ import { McpConfigError } from "../src/mcp-config.ts";
22
+ import { MissingMcpAdapterError } from "../src/mcp-coordination.ts";
23
+ import { CatalogError } from "../src/profile-catalog.ts";
24
+ import { ActivationError } from "../src/profile-resolver.ts";
25
+ import { generateRuntimeDir } from "../src/settings-generator.ts";
26
+
27
+ try {
28
+ const args = parseLauncherArgs(process.argv.slice(2));
29
+ const agentDir = getAgentDir();
30
+ // Fails before spawning when the profile is unknown or cannot activate.
31
+ // --approve/--no-approve are consumed here as a one-run trust input.
32
+ const { plan, discovery, projectSettings, warnings } = await resolveInitialProfile(args.profile, {
33
+ agentDir,
34
+ cwd: process.cwd(),
35
+ trustOverride: args.trustOverride,
36
+ });
37
+ for (const warning of warnings) {
38
+ console.error(`pi-profile: warning: ${warning}`);
39
+ }
40
+ // Stale per-launch runtime dirs (dead pid, or no pid past the grace
41
+ // window) are swept before this launch materializes its own. Best-effort:
42
+ // sweep errors never block the launch.
43
+ await sweepStaleRuntimeDirs(agentDir);
44
+ const generated = await generateRuntimeDir(plan, { agentDir, discovery, projectSettings });
45
+ process.exitCode = await spawnPi({
46
+ generated,
47
+ piArgs: args.piArgs,
48
+ // Only the default profile keeps trust behavior native (flag re-applied);
49
+ // named profiles never forward it — the resolver is the trust gatekeeper.
50
+ trustOverride: plan.filter === "none" ? args.trustOverride : undefined,
51
+ });
52
+ } catch (error) {
53
+ // Launcher input/selection failures (unknown profile, unresolvable
54
+ // references, missing adapter, malformed catalogs) are usage errors:
55
+ // exit 2. Unexpected failures: exit 1.
56
+ const isUsageError =
57
+ error instanceof UnknownProfileError ||
58
+ error instanceof ActivationError ||
59
+ error instanceof ExtensionError ||
60
+ error instanceof MissingMcpAdapterError ||
61
+ error instanceof McpConfigError ||
62
+ error instanceof CatalogError;
63
+ console.error(error instanceof Error ? `pi-profile: ${error.message}` : error);
64
+ process.exitCode = isUsageError ? 2 : 1;
65
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Types for the plain-JS install-time seeder (bin/postinstall.js), which
3
+ * stays dependency-free and cannot ship TypeScript.
4
+ */
5
+
6
+ export interface InstallResult {
7
+ path: string;
8
+ written: boolean;
9
+ /** Present when skipped to avoid shadowing a legacy catalog. */
10
+ skipped?: "legacy-catalog-present";
11
+ }
12
+
13
+ export function installDefaultProfiles(options?: { env?: NodeJS.ProcessEnv }): Promise<InstallResult>;
@@ -0,0 +1,88 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * postinstall: seed the global catalog with the default profiles file.
4
+ *
5
+ * Runs at package install time (`npm install pi-profile-switch` / `pi install`).
6
+ * Idempotent and conservative:
7
+ * - Writes `defaults/profiles.json` to the profile-switch dir ONLY when no
8
+ * catalog exists there yet (COPYFILE_EXCL; an existing file — including
9
+ * one written concurrently — is never touched).
10
+ * - Skips entirely when a legacy `~/.pi/agent/profiles.json` exists, because
11
+ * the resolver still falls back to it and a new preferred-path file would
12
+ * silently shadow it (see src/workspace.ts resolveGlobalProfilesPath).
13
+ * - Never fails the install: errors are downgraded to a warning.
14
+ *
15
+ * This module is plain Node ESM (no jiti/TS): npm may run postinstall in a
16
+ * context where only plain JS is safe. The path rules intentionally mirror
17
+ * src/workspace.ts — keep them in sync.
18
+ */
19
+ import { access, copyFile, mkdir } from "node:fs/promises";
20
+ import { constants } from "node:fs";
21
+ import { homedir } from "node:os";
22
+ import path from "node:path";
23
+ import { fileURLToPath, pathToFileURL } from "node:url";
24
+
25
+ const DEFAULT_TEMPLATE = fileURLToPath(new URL("../defaults/profiles.json", import.meta.url));
26
+
27
+ /** Mirrors getProfileSwitchDir() in src/workspace.ts.
28
+ * @param {NodeJS.ProcessEnv} env */
29
+ function profileSwitchDir(env) {
30
+ const override = env.PI_PROFILE_SWITCH_DIR;
31
+ if (override && override.trim()) return path.resolve(override.trim());
32
+ return path.join(homedir(), ".pi-profile-switch");
33
+ }
34
+
35
+ /** The legacy migration fallback the resolver still reads (~/.pi/agent).
36
+ * @param {NodeJS.ProcessEnv} env */
37
+ function legacyCatalogPath(env) {
38
+ const agentDir = env.PI_CODING_AGENT_DIR ?? path.join(homedir(), ".pi", "agent");
39
+ return path.join(agentDir, "profiles.json");
40
+ }
41
+
42
+ /** @param {string} filePath */
43
+ async function exists(filePath) {
44
+ try {
45
+ await access(filePath);
46
+ return true;
47
+ } catch {
48
+ return false;
49
+ }
50
+ }
51
+
52
+ /** @typedef {{ path: string, written: boolean, skipped?: string }} InstallResult */
53
+
54
+ /**
55
+ * Seeds the default catalog. Safe to call repeatedly; only the first call
56
+ * writes.
57
+ * @param {{ env?: NodeJS.ProcessEnv }} [options]
58
+ * @returns {Promise<InstallResult>}
59
+ */
60
+ export async function installDefaultProfiles({ env = process.env } = {}) {
61
+ const target = path.join(profileSwitchDir(env), "profiles.json");
62
+ if (await exists(target)) return { path: target, written: false };
63
+ if (await exists(legacyCatalogPath(env))) {
64
+ return { path: target, written: false, skipped: "legacy-catalog-present" };
65
+ }
66
+ await mkdir(path.dirname(target), { recursive: true });
67
+ try {
68
+ await copyFile(DEFAULT_TEMPLATE, target, constants.COPYFILE_EXCL);
69
+ } catch (error) {
70
+ // Lost a create race (e.g. two installs in parallel): the winner's
71
+ // file stands; still not ours to overwrite.
72
+ if (error.code === "EEXIST") return { path: target, written: false };
73
+ throw error;
74
+ }
75
+ return { path: target, written: true };
76
+ }
77
+
78
+ const invokedDirectly = process.argv[1] !== undefined && import.meta.url === pathToFileURL(process.argv[1]).href;
79
+ if (invokedDirectly) {
80
+ installDefaultProfiles().then(
81
+ (result) => {
82
+ if (result.written) console.log(`pi-profile: seeded default profiles at ${result.path}`);
83
+ },
84
+ (error) => {
85
+ console.warn(`pi-profile: could not seed default profiles: ${error instanceof Error ? error.message : error}`);
86
+ },
87
+ );
88
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "profiles": {
4
+ "ask": {
5
+ "label": "Ask & Discuss",
6
+ "description": "Read-only Q&A and code exploration; no file modifications or command execution",
7
+ "skills": [],
8
+ "extensions": [],
9
+ "tools": [
10
+ "read",
11
+ "grep",
12
+ "find",
13
+ "ls"
14
+ ],
15
+ "instructions": "You are in read-only discussion mode. Answer questions and explain code without modifying any files or running shell commands."
16
+ }
17
+ }
18
+ }
@@ -1,16 +1,42 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
3
  "profiles": {
4
- "read-only": {
5
- "label": "Read-only",
6
- "description": "Read-only session; no skills or MCP servers assumed — add your own.",
4
+ "review": {
5
+ "label": "Code review",
6
+ "description": "Read-heavy review workflow",
7
+ "skills": [
8
+ "code-review",
9
+ "diagnosing-bugs"
10
+ ],
11
+ "extensions": [
12
+ "pi-mcp-adapter"
13
+ ],
14
+ "mcps": [
15
+ "github"
16
+ ],
7
17
  "tools": [
8
18
  "read",
9
19
  "grep",
10
20
  "find",
11
- "ls"
21
+ "bash"
22
+ ],
23
+ "instructions": "Review only; do not modify files."
24
+ },
25
+ "impl": {
26
+ "label": "Implementation",
27
+ "skills": [
28
+ "tdd"
29
+ ],
30
+ "extensions": [
31
+ "pi-mcp-adapter"
32
+ ],
33
+ "mcps": [
34
+ "github",
35
+ "linear"
12
36
  ],
13
- "instructions": "Read-only session: inspect and report; never create, edit, rename, or delete files.\nIf a change is needed, describe it in your reply instead of applying it.\nDo not run commands that modify state (installs, formatters, commits, pushes, network writes).\nPrefer an available skill or MCP tool when it fits the request; otherwise use the tools you have.\nGround claims in evidence: cite file:line and separate verified facts from inferences.\nReply in English."
37
+ "defaultProvider": "deepseek",
38
+ "defaultModel": "deepseek-v4-pro",
39
+ "defaultThinkingLevel": "high"
14
40
  }
15
41
  }
16
42
  }