happyskills 0.12.0 → 0.13.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.13.0] - 2026-03-12
11
+
12
+ ### Added
13
+ - Add `visibility` command to check or set a skill's visibility in the registry — supports `public`, `private`, and `workspace` values; alias `vis`
14
+ - Add `patch` method to API client for PATCH requests
15
+ - Add `patch_repo` function to repos API module
16
+
17
+ ## [0.12.1] - 2026-03-12
18
+
19
+ ### Fixed
20
+ - Fix `whoami` silently swallowing workspace listing errors — now surfaces a `workspace_error` field in JSON mode and prints a warning with auth hint in human mode
21
+ - Fix `check` and `refresh` reporting `status: "unknown"` for private skills the user owns but can't access due to auth issues — now reports `status: "no-access"` with a login hint
22
+
10
23
  ## [0.12.0] - 2026-03-12
11
24
 
12
25
  ### Added
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "happyskills",
3
- "version": "0.12.0",
3
+ "version": "0.13.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)",
package/src/api/client.js CHANGED
@@ -60,6 +60,7 @@ const request = (method, path, options = {}) => catch_errors(`API ${method} ${pa
60
60
  const get = (path, options) => request('GET', path, options)
61
61
  const post = (path, body, options) => request('POST', path, { ...options, body })
62
62
  const put = (path, body, options) => request('PUT', path, { ...options, body })
63
+ const patch = (path, body, options) => request('PATCH', path, { ...options, body })
63
64
  const del = (path, options) => request('DELETE', path, options)
64
65
 
65
- module.exports = { request, get, post, put, del, get_base_url }
66
+ module.exports = { request, get, post, put, patch, del, get_base_url }
package/src/api/repos.js CHANGED
@@ -58,4 +58,10 @@ const del_repo = (owner, name) => catch_errors(`Failed to delete ${owner}/${name
58
58
  return data
59
59
  })
60
60
 
61
- module.exports = { search, resolve_dependencies, clone, push, get_refs, get_repo, check_updates, del_repo }
61
+ const patch_repo = (owner, name, fields) => catch_errors(`Failed to update ${owner}/${name}`, async () => {
62
+ const [errors, data] = await client.patch(`/repos/${encodeURIComponent(owner)}/${encodeURIComponent(name)}`, fields)
63
+ if (errors) throw errors[errors.length - 1]
64
+ return data
65
+ })
66
+
67
+ module.exports = { search, resolve_dependencies, clone, push, get_refs, get_repo, check_updates, del_repo, patch_repo }
@@ -2,7 +2,7 @@ const { error: { catch_errors, wrap_errors: e } } = require('puffy-core')
2
2
  const { read_lock, get_all_locked_skills } = require('../lock/reader')
3
3
  const repos_api = require('../api/repos')
4
4
  const { gt } = require('../utils/semver')
5
- const { print_help, print_table, print_json, print_info, print_success, print_hint, code } = require('../ui/output')
5
+ const { print_help, print_table, print_json, print_info, print_success, print_warn, print_hint, code } = require('../ui/output')
6
6
  const { green, yellow, red } = require('../ui/colors')
7
7
  const { exit_with_error } = require('../utils/errors')
8
8
  const { find_project_root } = require('../config/paths')
@@ -68,7 +68,9 @@ const run = (args) => catch_errors('Check failed', async () => {
68
68
  } else {
69
69
  for (const [name, data] of to_check) {
70
70
  const info = batch_data?.results?.[name]
71
- if (!info || !info.latest_version) {
71
+ if (info?.access_denied) {
72
+ results.push({ skill: name, installed: data.version, latest: '-', status: 'no-access' })
73
+ } else if (!info || !info.latest_version) {
72
74
  results.push({ skill: name, installed: data.version, latest: '-', status: 'unknown' })
73
75
  } else if (info.latest_version === data.version) {
74
76
  results.push({ skill: name, installed: data.version, latest: info.latest_version, status: 'up-to-date' })
@@ -90,6 +92,7 @@ const run = (args) => catch_errors('Check failed', async () => {
90
92
  const status_colors = {
91
93
  'up-to-date': green,
92
94
  'outdated': yellow,
95
+ 'no-access': yellow,
93
96
  'error': red,
94
97
  'unknown': (s) => s
95
98
  }
@@ -104,6 +107,7 @@ const run = (args) => catch_errors('Check failed', async () => {
104
107
  print_table(['Skill', 'Installed', 'Latest', 'Status'], rows)
105
108
 
106
109
  const outdated = results.filter(r => r.status === 'outdated')
110
+ const no_access = results.filter(r => r.status === 'no-access')
107
111
  if (outdated.length > 0) {
108
112
  console.log()
109
113
  print_info(`Run ${code('happyskills update')} to upgrade ${outdated.length} skill(s).`)
@@ -111,6 +115,11 @@ const run = (args) => catch_errors('Check failed', async () => {
111
115
  console.log()
112
116
  print_success('All skills are up to date.')
113
117
  }
118
+ if (no_access.length > 0) {
119
+ console.log()
120
+ print_warn('Some skills require authentication.')
121
+ print_hint(`Run ${code('happyskills login')} to refresh your session.`)
122
+ }
114
123
  }).then(([errors]) => { if (errors) { exit_with_error(errors); return } })
115
124
 
116
125
  module.exports = { run }
@@ -3,7 +3,7 @@ const { install } = require('../engine/installer')
3
3
  const { read_lock, get_all_locked_skills } = require('../lock/reader')
4
4
  const repos_api = require('../api/repos')
5
5
  const { gt } = require('../utils/semver')
6
- const { print_help, print_table, print_json, print_info, print_success, code } = require('../ui/output')
6
+ const { print_help, print_table, print_json, print_info, print_success, print_warn, print_hint, code } = require('../ui/output')
7
7
  const { green, yellow, red } = require('../ui/colors')
8
8
  const { create_spinner } = require('../ui/spinner')
9
9
  const { exit_with_error } = require('../utils/errors')
@@ -74,7 +74,9 @@ const run = (args) => catch_errors('Refresh failed', async () => {
74
74
  spinner?.succeed('Checked for updates')
75
75
  for (const [name, data] of to_check) {
76
76
  const info = batch_data?.results?.[name]
77
- if (!info || !info.latest_version) {
77
+ if (info?.access_denied) {
78
+ results.push({ skill: name, installed: data.version, latest: '-', status: 'no-access' })
79
+ } else if (!info || !info.latest_version) {
78
80
  results.push({ skill: name, installed: data.version, latest: '-', status: 'unknown' })
79
81
  } else if (info.latest_version === data.version) {
80
82
  results.push({ skill: name, installed: data.version, latest: info.latest_version, status: 'up-to-date' })
@@ -105,7 +107,7 @@ const run = (args) => catch_errors('Refresh failed', async () => {
105
107
 
106
108
  // 3. Show results table (non-json)
107
109
  if (!args.flags.json) {
108
- const status_colors = { 'up-to-date': green, 'outdated': yellow, 'error': red, 'unknown': (s) => s }
110
+ const status_colors = { 'up-to-date': green, 'outdated': yellow, 'no-access': yellow, 'error': red, 'unknown': (s) => s }
109
111
  print_table(['Skill', 'Installed', 'Latest', 'Status'], results.map(r => [
110
112
  r.skill, r.installed, r.latest, (status_colors[r.status] || ((s) => s))(r.status)
111
113
  ]))
@@ -0,0 +1,79 @@
1
+ const { error: { catch_errors } } = require('puffy-core')
2
+ const { get_repo, patch_repo } = require('../api/repos')
3
+ const { require_token } = require('../auth/token_store')
4
+ const { print_help, print_json, print_success } = require('../ui/output')
5
+ const { exit_with_error, UsageError } = require('../utils/errors')
6
+ const { EXIT_CODES } = require('../constants')
7
+
8
+ const VALID_VISIBILITIES = ['public', 'private', 'workspace']
9
+
10
+ const HELP_TEXT = `Usage: happyskills visibility <owner/name> [visibility] [options]
11
+
12
+ Check or set a skill's visibility in the registry.
13
+
14
+ Arguments:
15
+ owner/name Skill to check or update (e.g., acme/deploy-aws)
16
+ visibility New visibility: public, private, or workspace (omit to check current)
17
+
18
+ Options:
19
+ --json Output as JSON
20
+
21
+ Aliases: vis
22
+
23
+ Examples:
24
+ happyskills visibility acme/deploy-aws
25
+ happyskills visibility acme/deploy-aws public
26
+ happyskills vis acme/deploy-aws --json`
27
+
28
+ const run = (args) => catch_errors('Visibility failed', async () => {
29
+ if (args.flags._show_help) {
30
+ print_help(HELP_TEXT)
31
+ return process.exit(EXIT_CODES.SUCCESS)
32
+ }
33
+
34
+ const skill = args._[0]
35
+ if (!skill) {
36
+ throw new UsageError('Please specify a skill (e.g., happyskills visibility acme/deploy-aws).')
37
+ }
38
+
39
+ if (!skill.includes('/')) {
40
+ throw new UsageError('Skill must be in owner/name format (e.g., acme/deploy-aws).')
41
+ }
42
+
43
+ await require_token()
44
+
45
+ const [owner, name] = skill.split('/')
46
+ const new_visibility = args._[1]
47
+
48
+ if (new_visibility) {
49
+ if (!VALID_VISIBILITIES.includes(new_visibility)) {
50
+ throw new UsageError(`Invalid visibility "${new_visibility}". Must be one of: ${VALID_VISIBILITIES.join(', ')}.`)
51
+ }
52
+
53
+ const [errors, result] = await patch_repo(owner, name, { visibility: new_visibility })
54
+ if (errors) throw errors[errors.length - 1]
55
+
56
+ const visibility = result?.visibility || new_visibility
57
+
58
+ if (args.flags.json) {
59
+ print_json({ data: { skill, visibility } })
60
+ return
61
+ }
62
+
63
+ print_success(`Visibility for "${skill}" set to ${visibility}.`)
64
+ } else {
65
+ const [errors, result] = await get_repo(owner, name, { auth: true })
66
+ if (errors) throw errors[errors.length - 1]
67
+
68
+ const visibility = result?.visibility || 'unknown'
69
+
70
+ if (args.flags.json) {
71
+ print_json({ data: { skill, visibility } })
72
+ return
73
+ }
74
+
75
+ print_success(`Visibility for "${skill}" is ${visibility}.`)
76
+ }
77
+ }).then(([errors]) => { if (errors) { exit_with_error(errors); return } })
78
+
79
+ module.exports = { run }
@@ -1,7 +1,7 @@
1
1
  const { error: { catch_errors, wrap_errors: e } } = require('puffy-core')
2
2
  const { require_token } = require('../auth/token_store')
3
3
  const workspaces_api = require('../api/workspaces')
4
- const { print_help, print_label, print_json } = require('../ui/output')
4
+ const { print_help, print_label, print_json, print_warn, print_hint } = require('../ui/output')
5
5
  const { exit_with_error } = require('../utils/errors')
6
6
  const { EXIT_CODES } = require('../constants')
7
7
 
@@ -42,14 +42,20 @@ const run = (args) => catch_errors('Whoami failed', async () => {
42
42
  const [ws_err, workspaces] = await workspaces_api.list_workspaces()
43
43
 
44
44
  if (args.flags.json) {
45
- print_json({ data: { username, email, workspaces: ws_err ? [] : workspaces } })
45
+ const json_data = { username, email, workspaces: ws_err ? [] : workspaces }
46
+ if (ws_err) json_data.workspace_error = ws_err.map(er => er.message).join(': ')
47
+ print_json({ data: json_data })
46
48
  return
47
49
  }
48
50
 
49
51
  print_label('Username', username)
50
52
  print_label('Email', email)
51
53
 
52
- if (!ws_err && workspaces && workspaces.length > 0) {
54
+ if (ws_err) {
55
+ console.log()
56
+ print_warn(`Failed to list workspaces: ${ws_err.map(er => er.message).join(': ')}`)
57
+ print_hint('Check your authentication with happyskills login')
58
+ } else if (workspaces && workspaces.length > 0) {
53
59
  console.log()
54
60
  print_label('Workspaces', '')
55
61
  for (const ws of workspaces) {
package/src/constants.js CHANGED
@@ -34,7 +34,8 @@ const COMMAND_ALIASES = {
34
34
  up: 'update',
35
35
  pub: 'publish',
36
36
  v: 'validate',
37
- del: 'delete'
37
+ del: 'delete',
38
+ vis: 'visibility'
38
39
  }
39
40
 
40
41
  const COMMANDS = [
@@ -42,6 +43,7 @@ const COMMANDS = [
42
43
  'install',
43
44
  'uninstall',
44
45
  'delete',
46
+ 'visibility',
45
47
  'list',
46
48
  'search',
47
49
  'check',
package/src/index.js CHANGED
@@ -88,6 +88,7 @@ Commands:
88
88
  install [owner/skill] Install skill + dependencies (alias: i, add)
89
89
  uninstall <owner/skill> Remove skill + prune orphans (alias: rm, remove)
90
90
  delete <owner/skill> Delete skill from registry (alias: del)
91
+ visibility <owner/skill> Check or set visibility (alias: vis)
91
92
  list List installed skills (alias: ls)
92
93
  search <query> Search the registry (alias: s)
93
94
  check [owner/skill] Check for available updates