happyskills 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/CHANGELOG.md CHANGED
@@ -7,6 +7,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/).
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.4.0] - 2026-03-05
11
+
12
+ ### Added
13
+ - Add `-g` / `--global` flag to `setup` command to opt into installing `happyskillsai/happyskills-cli` globally (`~/.claude/skills/`)
14
+
15
+ ### Changed
16
+ - Change `setup` default install scope from global to project-level (`.claude/skills/`); pass `-g` to restore the previous global behaviour
17
+
18
+ ## [0.3.1] - 2026-03-04
19
+
20
+ ### Fixed
21
+ - Fix global installs writing `skills-lock.json` to the local project directory instead of `~/.claude/`; global lock state is now read/written from `~/.claude/skills-lock.json` across `install`, `uninstall`, `list`, `update`, and `convert` commands
22
+
10
23
  ## [0.3.0] - 2026-03-04
11
24
 
12
25
  ### Added
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "happyskills",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "Package manager for AI agent skills",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "author": "Nicolas Dao <nic@cloudlesslabs.com> (https://cloudlesslabs.com)",
@@ -12,7 +12,7 @@ const { write_manifest } = require('../manifest/writer')
12
12
  const { read_lock } = require('../lock/reader')
13
13
  const { write_lock, update_lock_skills } = require('../lock/writer')
14
14
  const { hash_directory } = require('../lock/integrity')
15
- const { skills_dir, find_project_root } = require('../config/paths')
15
+ const { skills_dir, find_project_root, lock_root } = require('../config/paths')
16
16
  const { file_exists } = require('../utils/fs')
17
17
  const { create_spinner } = require('../ui/spinner')
18
18
  const { print_help, print_success, print_error, print_info, print_label, print_json } = require('../ui/output')
@@ -154,7 +154,8 @@ const run = (args) => catch_errors('Convert failed', async () => {
154
154
  if (push_err) { pub_spinner.fail('Publish failed'); throw e('Push failed', push_err) }
155
155
 
156
156
  pub_spinner.update('Updating lock file...')
157
- const [, lock_data] = await read_lock(project_root)
157
+ const lock_dir = lock_root(is_global, project_root)
158
+ const [, lock_data] = await read_lock(lock_dir)
158
159
  const [, integrity] = await hash_directory(skill_dir)
159
160
 
160
161
  const full_name = `${workspace.slug}/${skill_name}`
@@ -170,7 +171,7 @@ const run = (args) => catch_errors('Convert failed', async () => {
170
171
  }
171
172
 
172
173
  const new_skills = update_lock_skills(lock_data, updates)
173
- const [lock_err] = await write_lock(project_root, new_skills)
174
+ const [lock_err] = await write_lock(lock_dir, new_skills)
174
175
  if (lock_err) { pub_spinner.fail('Failed to write lock file'); throw e('Lock write failed', lock_err) }
175
176
 
176
177
  pub_spinner.succeed(`Converted ${full_name}@${version}`)
@@ -1,6 +1,6 @@
1
1
  const { error: { catch_errors, wrap_errors: e } } = require('puffy-core')
2
2
  const { read_lock, get_all_locked_skills } = require('../lock/reader')
3
- const { skills_dir, skill_install_dir, find_project_root } = require('../config/paths')
3
+ const { skills_dir, skill_install_dir, find_project_root, lock_root } = require('../config/paths')
4
4
  const { file_exists } = require('../utils/fs')
5
5
  const { scan_skills_dir } = require('../utils/skill_scanner')
6
6
  const { print_help, print_table, print_json, print_info } = require('../ui/output')
@@ -32,7 +32,7 @@ const run = (args) => catch_errors('List failed', async () => {
32
32
  const project_root = find_project_root()
33
33
  const base_dir = skills_dir(is_global, project_root)
34
34
 
35
- const [, lock_data] = await read_lock(project_root)
35
+ const [, lock_data] = await read_lock(lock_root(is_global, project_root))
36
36
  const skills = get_all_locked_skills(lock_data)
37
37
  const managed_entries = Object.entries(skills)
38
38
 
@@ -7,18 +7,20 @@ const { EXIT_CODES } = require('../constants')
7
7
 
8
8
  const SKILL_NAME = 'happyskillsai/happyskills-cli'
9
9
 
10
- const HELP_TEXT = `Usage: happyskills setup
10
+ const HELP_TEXT = `Usage: happyskills setup [options]
11
11
 
12
- Install (or update) the official HappySkills CLI skill globally.
12
+ Install (or update) the official HappySkills CLI skill.
13
13
 
14
- Installs happyskillsai/happyskills-cli into ~/.claude/skills/ so AI agents
14
+ Installs happyskillsai/happyskills-cli into .claude/skills/ so AI agents
15
15
  (Claude Code, etc.) can interact with HappySkills using natural language.
16
16
 
17
17
  Options:
18
+ -g, --global Install globally (~/.claude/skills/) instead of project-level
18
19
  --json Output as JSON
19
20
 
20
21
  Examples:
21
- happyskills setup`
22
+ happyskills setup
23
+ happyskills setup -g`
22
24
 
23
25
  const run = (args) => catch_errors('Setup failed', async () => {
24
26
  if (args.flags._show_help) {
@@ -27,7 +29,7 @@ const run = (args) => catch_errors('Setup failed', async () => {
27
29
  }
28
30
 
29
31
  const options = {
30
- global: true,
32
+ global: args.flags.global || false,
31
33
  yes: true,
32
34
  version: 'latest',
33
35
  project_root: find_project_root()
@@ -3,7 +3,7 @@ const { install } = require('../engine/installer')
3
3
  const { read_lock, get_all_locked_skills } = require('../lock/reader')
4
4
  const { print_help, print_success, print_info, print_json } = require('../ui/output')
5
5
  const { exit_with_error, UsageError } = require('../utils/errors')
6
- const { find_project_root } = require('../config/paths')
6
+ const { find_project_root, lock_root } = require('../config/paths')
7
7
  const { EXIT_CODES } = require('../constants')
8
8
 
9
9
  const HELP_TEXT = `Usage: happyskills update [owner/skill|--all] [options]
@@ -40,7 +40,8 @@ const run = (args) => catch_errors('Update failed', async () => {
40
40
  throw new UsageError("Specify a skill to update or use --all (e.g., 'happyskills update acme/deploy-aws' or 'happyskills update --all').")
41
41
  }
42
42
 
43
- const [, lock_data] = await read_lock(project_root)
43
+ const is_global = args.flags.global || false
44
+ const [, lock_data] = await read_lock(lock_root(is_global, project_root))
44
45
  const skills = get_all_locked_skills(lock_data)
45
46
 
46
47
  const to_update = target_skill
@@ -57,7 +58,7 @@ const run = (args) => catch_errors('Update failed', async () => {
57
58
  }
58
59
 
59
60
  const options = {
60
- global: args.flags.global || false,
61
+ global: is_global,
61
62
  fresh: true,
62
63
  project_root
63
64
  }
@@ -26,6 +26,10 @@ const tmp_dir = (base_skills_dir) => path.join(base_skills_dir, '.tmp')
26
26
 
27
27
  const install_lock_path = (base_skills_dir) => path.join(base_skills_dir, '.install.lock')
28
28
 
29
+ const lock_root = (is_global, project_root) => {
30
+ return is_global ? path.join(home_dir, '.claude') : project_root
31
+ }
32
+
29
33
  const lock_file_path = (project_root = process.cwd()) => path.join(project_root, 'skills-lock.json')
30
34
 
31
35
  const skill_install_dir = (base_skills_dir, name) => path.join(base_skills_dir, name)
@@ -60,6 +64,7 @@ module.exports = {
60
64
  skills_dir,
61
65
  tmp_dir,
62
66
  install_lock_path,
67
+ lock_root,
63
68
  lock_file_path,
64
69
  skill_install_dir,
65
70
  find_project_root
@@ -8,7 +8,7 @@ const { check_system_dependencies } = require('./system_deps')
8
8
  const { hash_directory, verify_integrity } = require('../lock/integrity')
9
9
  const { read_lock, get_locked_skill } = require('../lock/reader')
10
10
  const { write_lock, update_lock_skills } = require('../lock/writer')
11
- const { skills_dir, tmp_dir, skill_install_dir } = require('../config/paths')
11
+ const { skills_dir, tmp_dir, skill_install_dir, lock_root } = require('../config/paths')
12
12
  const { ensure_dir, remove_dir, file_exists } = require('../utils/fs')
13
13
  const { create_spinner } = require('../ui/spinner')
14
14
  const { print_success, print_warn } = require('../ui/output')
@@ -22,8 +22,9 @@ const install = (skill, options = {}) => catch_errors('Install failed', async ()
22
22
  const { version, global: is_global = false, force = false, fresh = false, project_root } = options
23
23
  const base_dir = skills_dir(is_global, project_root)
24
24
  const temp_dir = tmp_dir(base_dir)
25
+ const lock_dir = lock_root(is_global, project_root)
25
26
 
26
- const [, lock_data] = fresh ? [null, null] : await read_lock(project_root)
27
+ const [, lock_data] = fresh ? [null, null] : await read_lock(lock_dir)
27
28
 
28
29
  if (!fresh && lock_data) {
29
30
  const locked = get_locked_skill(lock_data, skill)
@@ -148,7 +149,7 @@ const install = (skill, options = {}) => catch_errors('Install failed', async ()
148
149
  }
149
150
 
150
151
  const new_skills = update_lock_skills(lock_data, updates)
151
- const [lock_errors] = await write_lock(project_root, new_skills)
152
+ const [lock_errors] = await write_lock(lock_dir, new_skills)
152
153
  if (lock_errors) { spinner.fail('Failed to write lock file'); throw e('Lock write failed', lock_errors) }
153
154
 
154
155
  spinner.succeed(`Installed ${packages_to_install.length} package(s)`)
@@ -1,7 +1,7 @@
1
1
  const { error: { catch_errors, wrap_errors: e } } = require('puffy-core')
2
2
  const { read_lock, get_locked_skill, get_all_locked_skills } = require('../lock/reader')
3
3
  const { write_lock, update_lock_skills } = require('../lock/writer')
4
- const { skills_dir, skill_install_dir } = require('../config/paths')
4
+ const { skills_dir, skill_install_dir, lock_root } = require('../config/paths')
5
5
  const { remove_dir } = require('../utils/fs')
6
6
  const { print_success, print_info } = require('../ui/output')
7
7
 
@@ -24,8 +24,9 @@ const find_orphans = (skills, removed_skill) => {
24
24
  const uninstall = (skill, options = {}) => catch_errors('Uninstall failed', async () => {
25
25
  const { global: is_global = false, project_root } = options
26
26
  const base_dir = skills_dir(is_global, project_root)
27
+ const lock_dir = lock_root(is_global, project_root)
27
28
 
28
- const [lock_errors, lock_data] = await read_lock(project_root)
29
+ const [lock_errors, lock_data] = await read_lock(lock_dir)
29
30
  if (lock_errors || !lock_data) {
30
31
  throw new Error('No skills-lock.json found. Nothing to uninstall.')
31
32
  }
@@ -58,7 +59,7 @@ const uninstall = (skill, options = {}) => catch_errors('Uninstall failed', asyn
58
59
  }
59
60
 
60
61
  const new_skills = update_lock_skills(lock_data, updates)
61
- const [write_errors] = await write_lock(project_root, new_skills)
62
+ const [write_errors] = await write_lock(lock_dir, new_skills)
62
63
  if (write_errors) throw e('Failed to update lock file', write_errors)
63
64
 
64
65
  print_success(`Removed ${skill}`)