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 +13 -0
- package/package.json +1 -1
- package/src/api/client.js +2 -1
- package/src/api/repos.js +7 -1
- package/src/commands/check.js +11 -2
- package/src/commands/refresh.js +5 -3
- package/src/commands/visibility.js +79 -0
- package/src/commands/whoami.js +9 -3
- package/src/constants.js +3 -1
- package/src/index.js +1 -0
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
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
|
-
|
|
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 }
|
package/src/commands/check.js
CHANGED
|
@@ -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 (
|
|
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 }
|
package/src/commands/refresh.js
CHANGED
|
@@ -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 (
|
|
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 }
|
package/src/commands/whoami.js
CHANGED
|
@@ -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
|
-
|
|
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 (
|
|
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
|