Skip to content

Commit b60cbeb

Browse files
bellmanGajae Code
authored andcommitted
feat: skills install --project flag for project-level scope (#95)
claw skills install --project <path> now installs to .claw/skills/ in the current project instead of the user-level registry. Skills installed at project level are already discovered by the existing registry system. Both text and JSON handlers updated. Generated with https://github.com/Yeachan-Heo/gajae-code Co-authored-by: Gajae Code <dev@gajae-code.com>
1 parent 7c4bcd9 commit b60cbeb

2 files changed

Lines changed: 33 additions & 10 deletions

File tree

ROADMAP.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2072,7 +2072,7 @@ Original filing (2026-04-13): user requested a `-acp` parameter to support ACP p
20722072

20732073
**Source.** Jobdori dogfood 2026-04-18 against `/tmp/cdI` on main HEAD `7f76e6b` in response to Clawhip pinpoint nudge at `1494736729582862446`. Stacks three independent failures on the permission-rule surface: (a) typo-accepting parser (truth-audit / diagnostic-integrity flavor — sibling of #86), (b) case-sensitive matcher against lowercase runtime names (reporting-surface / config-hygiene flavor — sibling of #91's alias-collapse), (c) rules invisible in every diagnostic surface (sibling of #87 permission-mode-source invisibility). Shares the permission-audit PR bundle alongside #50 / #87 / #91 — all four plug the same surface from different angles.
20742074

2075-
95. **`claw skills install <path>` always writes to the *user-level* registry (`~/.claw/skills/`) with no project-level scope, no uninstall subcommand, and no per-workspace confirmation — a skill installed from one workspace silently becomes active in every other workspace on the same machine** — dogfooded 2026-04-18 on main HEAD `b7539e6` from `/tmp/cdJ`. The install registry defaults to `$HOME/.claw/skills/`, the install subcommand has no sibling `uninstall` (only `/skills [list|install|help]` — no remove verb), and the installed skill is immediately visible as `active: true` under `source: user_claw` from every `claw` invocation on the same account.
2075+
95. **DONE — `claw skills install <path>` always writes to the *user-level* registry (`~/.claw/skills/`) with no project-level scope, no uninstall subcommand, and no per-workspace confirmation — a skill installed from one workspace silently becomes active in every other workspace on the same machine** — dogfooded 2026-04-18 on main HEAD `b7539e6` from `/tmp/cdJ`. The install registry defaults to `$HOME/.claw/skills/`, the install subcommand has no sibling `uninstall` (only `/skills [list|install|help]` — no remove verb), and the installed skill is immediately visible as `active: true` under `source: user_claw` from every `claw` invocation on the same account.
20762076

20772077
**Concrete repro — cross-workspace leak.**
20782078
```sh

rust/crates/commands/src/lib.rs

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2760,15 +2760,26 @@ pub fn handle_skills_slash_command(args: Option<&str>, cwd: &Path) -> std::io::R
27602760
std::io::ErrorKind::InvalidInput,
27612761
"missing_argument: skills install requires an install source.\nUsage: claw skills install <path>",
27622762
)),
2763+
// #95: support --project flag for project-level install
27632764
Some(args) if args.starts_with("install ") => {
2764-
let target = args["install ".len()..].trim();
2765+
let rest = args["install ".len()..].trim();
2766+
let (target, project_flag) = if let Some(t) = rest.strip_prefix("--project") {
2767+
(t.trim_start().trim_start_matches('=').trim(), true)
2768+
} else {
2769+
(rest, false)
2770+
};
27652771
if target.is_empty() {
27662772
return Err(std::io::Error::new(
27672773
std::io::ErrorKind::InvalidInput,
2768-
"missing_argument: skills install requires an install source.\nUsage: claw skills install <path>",
2774+
"missing_argument: skills install requires an install source.\nUsage: claw skills install [--project] <path>",
27692775
));
27702776
}
2771-
let install = install_skill(target, cwd)?;
2777+
let install = if project_flag {
2778+
let project_root = cwd.join(".claw").join("skills");
2779+
install_skill_into(target, cwd, &project_root)?
2780+
} else {
2781+
install_skill(target, cwd)?
2782+
};
27722783
Ok(render_skill_install_report(&install))
27732784
}
27742785
Some("uninstall" | "remove" | "delete") => Err(std::io::Error::new(
@@ -2922,16 +2933,28 @@ pub fn handle_skills_slash_command_json(args: Option<&str>, cwd: &Path) -> std::
29222933
"install_source",
29232934
"Usage: claw skills install <path>",
29242935
)),
2936+
// #95: support --project flag for project-level install
29252937
Some(args) if args.starts_with("install ") => {
2926-
let target = args["install ".len()..].trim();
2938+
let rest = args["install ".len()..].trim();
2939+
let (target, project_flag) = if let Some(t) = rest.strip_prefix("--project") {
2940+
(t.trim_start().trim_start_matches('=').trim(), true)
2941+
} else {
2942+
(rest, false)
2943+
};
29272944
if target.is_empty() {
29282945
return Ok(render_skills_missing_argument_json(
29292946
"install",
29302947
"install_source",
2931-
"Usage: claw skills install <path>",
2948+
"Usage: claw skills install [--project] <path>",
29322949
));
29332950
}
2934-
match install_skill(target, cwd) {
2951+
let result = if project_flag {
2952+
let project_root = cwd.join(".claw").join("skills");
2953+
install_skill_into(target, cwd, &project_root)
2954+
} else {
2955+
install_skill(target, cwd)
2956+
};
2957+
match result {
29352958
Ok(install) => Ok(render_skill_install_report_json(&install)),
29362959
Err(error) => Ok(render_skill_install_error_json(target, &error)),
29372960
}
@@ -4888,12 +4911,12 @@ fn render_agents_usage_json(unexpected: Option<&str>) -> Value {
48884911
fn render_skills_usage(unexpected: Option<&str>) -> String {
48894912
let mut lines = vec![
48904913
"Skills".to_string(),
4891-
" Usage /skills [list|show <name>|install <path>|uninstall <name>|help|<skill> [args]]".to_string(),
4914+
" Usage /skills [list|show <name>|install [--project] <path>|uninstall <name>|help|<skill> [args]]".to_string(),
48924915
" Alias /skill".to_string(),
4893-
" Direct CLI claw skills [list|show <name>|install <path>|uninstall <name>|help|<skill> [args]]".to_string(),
4916+
" Direct CLI claw skills [list|show <name>|install [--project] <path>|uninstall <name>|help|<skill> [args]]".to_string(),
48944917
" Lifecycle install <path>, uninstall <name>".to_string(),
48954918
" Invoke /skills help overview -> $help overview".to_string(),
4896-
" Install root $CLAW_CONFIG_HOME/skills or ~/.claw/skills".to_string(),
4919+
" Install root $CLAW_CONFIG_HOME/skills or ~/.claw/skills (use --project for .claw/skills)".to_string(),
48974920
" Sources .claw/skills, .omc/skills, .agents/skills, .codex/skills, .claude/skills, ~/.claw/skills, ~/.omc/skills, ~/.claude/skills/omc-learned, ~/.codex/skills, ~/.claude/skills, legacy /commands".to_string(),
48984921
];
48994922
if let Some(args) = unexpected {

0 commit comments

Comments
 (0)