Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ under `.pi/npm` and `.pi/git`, while `--global` uses the configured rpi agent
directory's `npm` and `git` stores. Local directories are enabled in place and
are never copied or deleted. Existing legacy `.rpi/packages`, `.pi/packages`,
and native `~/.pi/agent` installs remain discoverable. Package-manager argv is
selected from trusted `.rpi/settings.json`, trusted `.pi/settings.json`, then
selected from project `.rpi/settings.json`, project `.pi/settings.json`, then
global `settings.json`; the default is npm. rpi treats the setting as structured
argv rather than a shell command string, applies hardened encoding to Windows
`.cmd` shims, and uses the native Pi flags for npm, pnpm, or bun.
Expand Down
2 changes: 1 addition & 1 deletion crates/pi-cli/embedded-docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ under `.pi/npm` and `.pi/git`, while `--global` uses the configured rpi agent
directory's `npm` and `git` stores. Local directories are enabled in place and
are never copied or deleted. Existing legacy `.rpi/packages`, `.pi/packages`,
and native `~/.pi/agent` installs remain discoverable. Package-manager argv is
selected from trusted `.rpi/settings.json`, trusted `.pi/settings.json`, then
selected from project `.rpi/settings.json`, project `.pi/settings.json`, then
global `settings.json`; the default is npm. rpi treats the setting as structured
argv rather than a shell command string, applies hardened encoding to Windows
`.cmd` shims, and uses the native Pi flags for npm, pnpm, or bun.
Expand Down
9 changes: 5 additions & 4 deletions crates/pi-cli/embedded-docs/user-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,8 +145,9 @@ rpi install <crate>
rpi install-pi <spec>
rpi uninstall <crate>
rpi uninstall-pi <spec>
rpi update # 更新 Rust/npm package
rpi pi-update # 更新 rpi CLI 自身
rpi update # 只更新 Rust 原生扩展
rpi pi-update # 只更新 Pi npm/Git package
rpi self-update # 更新 rpi CLI 自身
```

## 4. 内置工具
Expand Down Expand Up @@ -249,12 +250,12 @@ rpi uninstall pi npm:@scope/package
rpi package add ../my-pi-package
rpi package list
rpi package remove ../my-pi-package
rpi package update
rpi package update # 兼容入口:同时更新 Rust 原生扩展和 Pi package
```

npm 和 Git 安装使用与原生 Pi 一致的托管布局:项目范围分别写入 `.pi/npm`、`.pi/git`,`--global` 则写入当前 rpi agent 配置目录下的 `npm`、`git`。本地目录只记录到 settings,不会复制,也不会在卸载时删除。旧版 `.rpi/packages`、`.pi/packages` 以及 `~/.pi/agent` 下的原生 Pi 安装仍可发现和迁移。

`npmCommand` 是 argv 数组,不是 shell 字符串;依次选择已信任项目的 `.rpi/settings.json`、`.pi/settings.json`、全局 `settings.json`,都未配置时使用 npm。rpi 会按识别到的 npm、pnpm 或 bun 生成与原生 Pi 一致的 install/uninstall 参数。未信任项目的 settings 和 package 路径不会参与解析;无法安全验证的路径、来源或 manifest 会直接拒绝。Node.js 是运行 JS/TS extension 的必要条件。普通 rpi 命令不会加载这些 package,需显式传 `--enable-pi-packages`。
`npmCommand` 是 argv 数组,不是 shell 字符串;依次选择项目 `.rpi/settings.json`、`.pi/settings.json`、全局 `settings.json`,都未配置时使用 npm。项目资源默认直接加载,不会弹出确认;需要临时禁用时使用 `--no-approve`,或在 TUI 中执行 `/trust no`。rpi 会按识别到的 npm、pnpm 或 bun 生成与原生 Pi 一致的 install/uninstall 参数。无法安全验证的路径、来源或 manifest 会直接拒绝。Node.js 是运行 JS/TS extension 的必要条件。普通 rpi 命令不会加载这些 package,需显式传 `--enable-pi-packages`。

### 静态资源

Expand Down
49 changes: 6 additions & 43 deletions crates/pi-cli/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
//!
//! # v1 scope cuts vs TS `main.ts` (in `docs/m6-cli-open-questions.md`)
//!
//! The TS `main` is enormous: HTTP proxy config, project-trust prompts,
//! The TS `main` is enormous: HTTP proxy config, project-trust handling,
//! first-time setup, migrations, and full npm package management remain
//! outside this port. rpi does support local static package management via
//! `rpi package` and Rust cdylib extension installation. The regular agent path
Expand All @@ -25,7 +25,7 @@
//! but not attached to the prompt — the harness `prompt_text` accepts images,
//! but v1 does not yet wire an image processor; binary/non-UTF-8 files error).

use std::io::{IsTerminal, Read, Write};
use std::io::{IsTerminal, Read};
use std::path::Path;

use rpi_ai::types::{ImageContent, ImageContentType};
Expand Down Expand Up @@ -97,14 +97,12 @@ pub async fn run() -> i32 {
return crate::packages::run_cli(&argv[1..]);
}
if argv.first().map(|s| s.as_str()) == Some("update") {
// The top-level update command owns Rust/npm package updates.
// `pi-update` is the explicit self-update command for rpi itself.
let mut package_args = Vec::with_capacity(argv.len());
package_args.push("update".to_string());
package_args.extend_from_slice(&argv[1..]);
return crate::packages::run_cli(&package_args);
return crate::packages::run_native_update(&argv[1..]);
}
if argv.first().map(|s| s.as_str()) == Some("pi-update") {
return crate::packages::run_pi_update(&argv[1..]);
}
if argv.first().map(|s| s.as_str()) == Some("self-update") {
return crate::updates::run_self_update(&argv[1..]);
}
if argv.first().map(|s| s.as_str()) == Some("install") {
Expand Down Expand Up @@ -215,25 +213,6 @@ pub async fn run() -> i32 {
None
};

// Native Pi asks before loading project-local settings/resources. Only
// prompt when an interactive terminal is available and there is something
// project-owned to authorize; headless/print/json invocations remain
// fail-closed without blocking for input.
if parsed.trust_override.is_none()
&& std::io::stdin().is_terminal()
&& std::io::stdout().is_terminal()
&& crate::session::project_has_local_resources(&cwd)
{
match prompt_project_trust(&cwd) {
Some(decision) => parsed.trust_override = Some(decision),
None => {
eprintln!(
"warning: project trust prompt unavailable; local resources remain disabled"
);
}
}
}

// `-r/--resume` is an interactive picker, unlike `-c/--continue` which
// immediately opens the latest session. Resolve the picker result before
// building the harness so cancelling does not create or modify a session.
Expand Down Expand Up @@ -446,22 +425,6 @@ pub async fn run() -> i32 {
exit_code
}

fn prompt_project_trust(cwd: &Path) -> Option<bool> {
let display = cwd.display();
print!("Trust project {display} and load local resources? [y/N] ");
let _ = std::io::stdout().flush();
let mut answer = String::new();
if std::io::stdin().read_line(&mut answer).is_err() {
return None;
}
let normalized = answer.trim().to_ascii_lowercase();
let trusted = matches!(normalized.as_str(), "y" | "yes");
if let Err(error) = crate::config::set_project_trust(cwd, Some(trusted)) {
eprintln!("warning: could not persist project trust decision: {error}");
}
Some(trusted)
}

/// Print the merged model catalog, optionally filtered by a case-insensitive
/// fuzzy-ish substring over provider, id, and display name.
async fn list_models(search: &str) -> i32 {
Expand Down
9 changes: 5 additions & 4 deletions crates/pi-cli/src/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -582,8 +582,8 @@ pub fn print_help() {
--list-models [search] List available models (with optional fuzzy search)
--offline Disable startup network operations (same as PI_OFFLINE=1)
--export <file> Export a JSONL session to HTML and exit
--approve, -a Trust the current project for local resources
--no-approve, -na Do not trust the current project
--approve, -a Force-enable current-project resources
--no-approve, -na Disable current-project resources
--print, -p Non-interactive: process prompt(s) and exit
--continue, -c Continue the most recent session
--resume, -r Browse and select a session to resume
Expand All @@ -608,8 +608,9 @@ pub fn print_help() {
--version, -v Show version

{u}Subcommands:{r}
update Update installed Rust and npm packages
pi-update Update the rpi CLI from crates.io
update Update installed Rust-native extensions
pi-update Update configured Pi npm/Git packages
self-update Update the rpi CLI from crates.io
auth login|check|logout Manage persisted credentials in ~/.rpi/auth.json
(see `rpi auth --help`)
package list|add|remove|update Manage TS packages and Rust extensions
Expand Down
17 changes: 9 additions & 8 deletions crates/pi-cli/src/install_pi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,13 +114,12 @@ pub fn run(args: &[String]) -> i32 {
}

fn project_trust_for_package_operation(cwd: &Path, global: bool) -> Result<bool, String> {
match crate::config::project_trust_decision(cwd) {
Ok(Some(true)) => Ok(true),
Ok(_) if global => Ok(false),
Ok(_) => Err("project is not trusted; refusing to access project package storage".into()),
Err(_) if global => Ok(false),
Err(error) => Err(format!("could not read project trust decision: {error}")),
if global {
return Ok(false);
}
crate::config::project_trust_decision(cwd)
.map(|decision| decision.unwrap_or(true))
.map_err(|error| format!("could not read project trust decision: {error}"))
}

fn classify_install_spec(cwd: &Path, spec: &str) -> InstallSpecKind {
Expand Down Expand Up @@ -3966,7 +3965,7 @@ mod tests {
}

#[test]
fn project_package_operations_require_saved_trust() {
fn project_package_operations_are_enabled_by_default() {
struct RestoreEnv(Option<std::ffi::OsString>);
impl Drop for RestoreEnv {
fn drop(&mut self) {
Expand All @@ -3986,8 +3985,10 @@ mod tests {
let _restore = RestoreEnv(std::env::var_os(crate::config::CONFIG_DIR_ENV));
std::env::set_var(crate::config::CONFIG_DIR_ENV, &agent);

assert!(project_trust_for_package_operation(&project, false).is_err());
assert!(project_trust_for_package_operation(&project, false).unwrap());
assert!(!project_trust_for_package_operation(&project, true).unwrap());
crate::config::set_project_trust(&project, Some(false)).unwrap();
assert!(!project_trust_for_package_operation(&project, false).unwrap());
crate::config::set_project_trust(&project, Some(true)).unwrap();
assert!(project_trust_for_package_operation(&project, false).unwrap());
}
Expand Down
Loading
Loading