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.
- package/README.md +32 -32
- package/README.zh-CN.md +32 -32
- package/bin/pi-profile.js +11 -0
- package/bin/pi-profile.ts +65 -0
- package/bin/postinstall.d.ts +13 -0
- package/bin/postinstall.js +88 -0
- package/defaults/profiles.json +18 -0
- package/examples/profiles.json +31 -5
- package/extensions/pi-profile/index.ts +451 -0
- package/package.json +10 -17
- package/schemas/profiles.schema.json +22 -24
- package/src/extension-discovery.ts +347 -0
- package/src/json-file.ts +1 -21
- package/src/launcher/args.ts +57 -0
- package/src/launcher/discovery.ts +64 -0
- package/src/launcher/initial-profile.ts +179 -0
- package/src/launcher/model-check.ts +52 -0
- package/src/launcher/runtime-cleanup.ts +85 -0
- package/src/launcher/spawn.ts +82 -0
- package/src/mcp-config.ts +37 -153
- package/src/mcp-coordination.ts +29 -10
- package/src/profile-catalog-store.ts +31 -12
- package/src/profile-catalog.ts +44 -63
- package/src/profile-resolver.ts +239 -245
- package/src/project-trust.ts +82 -0
- package/src/runtime-state-store.ts +25 -35
- package/src/settings-generator.ts +541 -0
- package/src/skill-registry.ts +94 -0
- package/src/switching/apply-plan.ts +197 -0
- package/src/switching/customize.ts +62 -33
- package/src/switching/list-profiles.ts +9 -6
- package/src/switching/mcp-toggle.ts +21 -25
- package/src/switching/profile-crud.ts +31 -24
- package/src/switching/profile-wizard.ts +21 -49
- package/src/switching/status.ts +142 -72
- package/src/switching/switch-profile.ts +219 -0
- package/src/switching/tool-references.ts +40 -0
- package/src/workspace.ts +57 -0
- package/LICENSE +0 -21
- package/examples/profiles.example.json +0 -74
- package/extensions/pi-profile-switch/index.ts +0 -744
- package/src/adapter-presence.ts +0 -75
- package/src/mcp-overlay-file.ts +0 -35
- package/src/mcp-overlay.ts +0 -122
- package/src/model-selection.ts +0 -64
- package/src/name-matching.ts +0 -50
- package/src/profile-badge.ts +0 -142
- package/src/profile-presets.ts +0 -61
- package/src/skill-selection.ts +0 -81
- package/src/startup-mcp-scope.ts +0 -262
- package/src/startup-selection.ts +0 -144
- package/src/switching/activate-profile.ts +0 -115
- 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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
#
|
|
23
|
-
pi
|
|
20
|
+
# Launch with the built-in default profile (all resources, plain Pi behavior)
|
|
21
|
+
pi-profile
|
|
24
22
|
|
|
25
|
-
#
|
|
26
|
-
pi
|
|
23
|
+
# Launch with a named profile
|
|
24
|
+
pi-profile review
|
|
27
25
|
|
|
28
|
-
#
|
|
29
|
-
pi --
|
|
26
|
+
# Anything after -- is passed to pi verbatim
|
|
27
|
+
pi-profile review -- --model openai/gpt-5.4
|
|
30
28
|
```
|
|
31
29
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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>`
|
|
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** —
|
|
71
|
-
- **Fail safe** — untrusted project directories are never read; a failed
|
|
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
|
|
5
|
+
[Pi](https://github.com/badlogic/pi-mono) 的命名 profile 扩展。一个 profile 引用一组已存在的 skills、extensions、MCP server 和 tools,并在同一 Pi 进程内切换,无需重启。
|
|
6
6
|
|
|
7
|
-
|
|
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
|
-
|
|
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
|
-
#
|
|
23
|
-
pi
|
|
20
|
+
# 使用内建 default profile 启动(全量资源,等同原生 Pi)
|
|
21
|
+
pi-profile
|
|
24
22
|
|
|
25
|
-
#
|
|
26
|
-
pi
|
|
23
|
+
# 使用指定 profile 启动
|
|
24
|
+
pi-profile review
|
|
27
25
|
|
|
28
|
-
#
|
|
29
|
-
pi --
|
|
26
|
+
# -- 后面的参数原样传给 pi
|
|
27
|
+
pi-profile review -- --model openai/gpt-5.4
|
|
30
28
|
```
|
|
31
29
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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
|
-
|
|
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>`
|
|
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
|
|
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
|
+
}
|
package/examples/profiles.json
CHANGED
|
@@ -1,16 +1,42 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
3
|
"profiles": {
|
|
4
|
-
"
|
|
5
|
-
"label": "
|
|
6
|
-
"description": "Read-
|
|
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
|
-
"
|
|
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
|
-
"
|
|
37
|
+
"defaultProvider": "deepseek",
|
|
38
|
+
"defaultModel": "deepseek-v4-pro",
|
|
39
|
+
"defaultThinkingLevel": "high"
|
|
14
40
|
}
|
|
15
41
|
}
|
|
16
42
|
}
|