happyskills 0.4.4 → 0.6.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 +24 -0
- package/package.json +1 -1
- package/src/api/push.js +3 -3
- package/src/api/repos.js +7 -2
- package/src/commands/convert.js +6 -20
- package/src/commands/publish.js +4 -1
- package/src/commands/search.js +41 -14
- package/src/index.js +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,30 @@ and this project adheres to [Semantic Versioning](https://semver.org/).
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.6.0] - 2026-03-06
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- Add `--public` flag to `publish` command to explicitly publish a skill as publicly discoverable in the catalog
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
- Change `publish` default visibility from public to private — skills are now private by default; pass `--public` to make a skill visible in the catalog
|
|
17
|
+
- Change `convert` to no longer auto-publish to the registry after converting; the skill is registered locally and the user is prompted to run `happyskills publish <skill-name>` as a separate step
|
|
18
|
+
|
|
19
|
+
### Fixed
|
|
20
|
+
- Fix `--version` flag collision where commands accepting `--version <value>` (e.g., `convert --version 1.0.0`) would incorrectly trigger the root `--version` output instead of passing the value to the command
|
|
21
|
+
|
|
22
|
+
## [0.5.0] - 2026-03-05
|
|
23
|
+
|
|
24
|
+
### Added
|
|
25
|
+
- Add `--mine` flag to `search` command to browse skills across all user's workspaces
|
|
26
|
+
- Add `--personal` flag to `search` command to browse skills in the user's personal workspace only
|
|
27
|
+
- Add `-w` / `--workspace <slug>` flag to `search` command to search within specific workspace(s)
|
|
28
|
+
- Add `--tags <tags>` flag to `search` command to filter results by tags
|
|
29
|
+
- Add `scope` and `visibility` fields to `search` JSON output
|
|
30
|
+
|
|
31
|
+
### Changed
|
|
32
|
+
- Change `search` query argument from required to optional when `--mine`, `--personal`, or `--workspace` is provided (browse mode)
|
|
33
|
+
|
|
10
34
|
## [0.4.4] - 2026-03-05
|
|
11
35
|
|
|
12
36
|
### Fixed
|
package/package.json
CHANGED
package/src/api/push.js
CHANGED
|
@@ -12,13 +12,13 @@ const estimate_payload_size = (files) => {
|
|
|
12
12
|
return size
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
const smart_push = (owner, repo, { version, message, files }, on_progress) =>
|
|
15
|
+
const smart_push = (owner, repo, { version, message, files, visibility }, on_progress) =>
|
|
16
16
|
catch_errors('Smart push failed', async () => {
|
|
17
17
|
const payload_size = estimate_payload_size(files)
|
|
18
18
|
|
|
19
19
|
if (payload_size < DIRECT_PUSH_THRESHOLD) {
|
|
20
20
|
// Small payload — use direct push
|
|
21
|
-
const [err, data] = await repos_api.push(owner, repo, { version, message, files })
|
|
21
|
+
const [err, data] = await repos_api.push(owner, repo, { version, message, files, visibility })
|
|
22
22
|
if (err) throw e('Direct push failed', err)
|
|
23
23
|
return data
|
|
24
24
|
}
|
|
@@ -28,7 +28,7 @@ const smart_push = (owner, repo, { version, message, files }, on_progress) =>
|
|
|
28
28
|
|
|
29
29
|
// Step 1: Initiate
|
|
30
30
|
const [init_err, init_data] = await initiate_upload(owner, repo, {
|
|
31
|
-
version, message, files: file_meta
|
|
31
|
+
version, message, files: file_meta, visibility
|
|
32
32
|
})
|
|
33
33
|
if (init_err) throw e('Upload initiation failed', init_err)
|
|
34
34
|
|
package/src/api/repos.js
CHANGED
|
@@ -2,10 +2,15 @@ const { error: { catch_errors } } = require('puffy-core')
|
|
|
2
2
|
const client = require('./client')
|
|
3
3
|
|
|
4
4
|
const search = (query, options = {}) => catch_errors('Search failed', async () => {
|
|
5
|
-
const params = new URLSearchParams(
|
|
5
|
+
const params = new URLSearchParams()
|
|
6
|
+
if (query) params.set('q', query)
|
|
6
7
|
if (options.limit) params.set('limit', options.limit)
|
|
7
8
|
if (options.offset) params.set('offset', options.offset)
|
|
8
|
-
|
|
9
|
+
if (options.scope) params.set('scope', options.scope)
|
|
10
|
+
if (options.workspace) params.set('workspace', options.workspace)
|
|
11
|
+
if (options.tags) params.set('tags', options.tags)
|
|
12
|
+
const needs_auth = (options.scope && options.scope !== 'public') || options.workspace
|
|
13
|
+
const [errors, data] = await client.get(`/repos/search?${params}`, { auth: needs_auth || false })
|
|
9
14
|
if (errors) throw errors[errors.length - 1]
|
|
10
15
|
return data
|
|
11
16
|
})
|
package/src/commands/convert.js
CHANGED
|
@@ -5,9 +5,7 @@ const { error: { catch_errors, wrap_errors: e } } = require('puffy-core')
|
|
|
5
5
|
const { require_token } = require('../auth/token_store')
|
|
6
6
|
const repos_api = require('../api/repos')
|
|
7
7
|
const workspaces_api = require('../api/workspaces')
|
|
8
|
-
const { smart_push } = require('../api/push')
|
|
9
8
|
const { parse_frontmatter } = require('../utils/skill_scanner')
|
|
10
|
-
const { collect_files } = require('../utils/file_collector')
|
|
11
9
|
const { write_manifest } = require('../manifest/writer')
|
|
12
10
|
const { read_lock } = require('../lock/reader')
|
|
13
11
|
const { write_lock, update_lock_skills } = require('../lock/writer')
|
|
@@ -125,7 +123,7 @@ const run = (args) => catch_errors('Convert failed', async () => {
|
|
|
125
123
|
console.error(` Description: ${description || '(none)'}`)
|
|
126
124
|
console.error(` Keywords: ${keywords.length ? keywords.join(', ') : '(none)'}`)
|
|
127
125
|
console.error('')
|
|
128
|
-
const answer = await confirm('
|
|
126
|
+
const answer = await confirm('Convert this skill to a managed package? [y/N] ')
|
|
129
127
|
if (answer !== 'y' && answer !== 'yes') {
|
|
130
128
|
print_info('Aborted.')
|
|
131
129
|
return process.exit(EXIT_CODES.SUCCESS)
|
|
@@ -138,21 +136,6 @@ const run = (args) => catch_errors('Convert failed', async () => {
|
|
|
138
136
|
const [manifest_err] = await write_manifest(skill_dir, manifest)
|
|
139
137
|
if (manifest_err) { pub_spinner.fail('Failed to write skill.json'); throw e('Failed to write skill.json', manifest_err) }
|
|
140
138
|
|
|
141
|
-
pub_spinner.update('Packaging skill...')
|
|
142
|
-
const [collect_err, skill_files] = await collect_files(skill_dir)
|
|
143
|
-
if (collect_err) { pub_spinner.fail('Failed to collect files'); throw e('File collection failed', collect_err) }
|
|
144
|
-
|
|
145
|
-
pub_spinner.update(`Publishing ${workspace.slug}/${skill_name}@${version}...`)
|
|
146
|
-
const on_progress = (completed, total) => {
|
|
147
|
-
pub_spinner.update(`Uploading files (${completed}/${total})...`)
|
|
148
|
-
}
|
|
149
|
-
const [push_err, push_data] = await smart_push(workspace.slug, skill_name, {
|
|
150
|
-
version,
|
|
151
|
-
message: `Release ${version}`,
|
|
152
|
-
files: skill_files
|
|
153
|
-
}, on_progress)
|
|
154
|
-
if (push_err) { pub_spinner.fail('Publish failed'); throw e('Push failed', push_err) }
|
|
155
|
-
|
|
156
139
|
pub_spinner.update('Updating lock file...')
|
|
157
140
|
const lock_dir = lock_root(is_global, project_root)
|
|
158
141
|
const [, lock_data] = await read_lock(lock_dir)
|
|
@@ -162,8 +145,8 @@ const run = (args) => catch_errors('Convert failed', async () => {
|
|
|
162
145
|
const updates = {
|
|
163
146
|
[full_name]: {
|
|
164
147
|
version,
|
|
165
|
-
ref:
|
|
166
|
-
commit:
|
|
148
|
+
ref: null,
|
|
149
|
+
commit: null,
|
|
167
150
|
integrity: integrity || null,
|
|
168
151
|
requested_by: ['__root__'],
|
|
169
152
|
dependencies: {}
|
|
@@ -185,6 +168,9 @@ const run = (args) => catch_errors('Convert failed', async () => {
|
|
|
185
168
|
} })
|
|
186
169
|
return
|
|
187
170
|
}
|
|
171
|
+
|
|
172
|
+
console.error('')
|
|
173
|
+
print_info(`Next step: enrich metadata, then run \`happyskills publish ${skill_name}\` to publish.`)
|
|
188
174
|
}).then(([errors]) => { if (errors) { exit_with_error(errors); return } })
|
|
189
175
|
|
|
190
176
|
module.exports = { run }
|
package/src/commands/publish.js
CHANGED
|
@@ -24,6 +24,7 @@ Arguments:
|
|
|
24
24
|
Options:
|
|
25
25
|
--bump <type> Auto-bump version before publishing (patch, minor, major)
|
|
26
26
|
--workspace <slug> Target workspace (overrides lock file owner)
|
|
27
|
+
--public Publish as public (default is private)
|
|
27
28
|
|
|
28
29
|
Aliases: pub
|
|
29
30
|
|
|
@@ -120,10 +121,12 @@ const run = (args) => catch_errors('Publish failed', async () => {
|
|
|
120
121
|
const on_progress = (completed, total) => {
|
|
121
122
|
spinner.update(`Uploading files (${completed}/${total})...`)
|
|
122
123
|
}
|
|
124
|
+
const visibility = args.flags.public ? 'public' : 'private'
|
|
123
125
|
const [push_err] = await smart_push(workspace.slug, manifest.name, {
|
|
124
126
|
version: manifest.version,
|
|
125
127
|
message: `Release ${manifest.version}`,
|
|
126
|
-
files: skill_files
|
|
128
|
+
files: skill_files,
|
|
129
|
+
visibility
|
|
127
130
|
}, on_progress)
|
|
128
131
|
if (push_err) { spinner.fail('Publish failed'); throw e('Push failed', push_err) }
|
|
129
132
|
|
package/src/commands/search.js
CHANGED
|
@@ -1,24 +1,31 @@
|
|
|
1
1
|
const { error: { catch_errors, wrap_errors: e } } = require('puffy-core')
|
|
2
2
|
const repos_api = require('../api/repos')
|
|
3
3
|
const { print_help, print_table, print_json, print_info } = require('../ui/output')
|
|
4
|
-
const { exit_with_error, UsageError } = require('../utils/errors')
|
|
4
|
+
const { exit_with_error, UsageError, AuthError } = require('../utils/errors')
|
|
5
5
|
const { EXIT_CODES } = require('../constants')
|
|
6
|
+
const { load_token } = require('../auth/token_store')
|
|
6
7
|
|
|
7
|
-
const HELP_TEXT = `Usage: happyskills search
|
|
8
|
+
const HELP_TEXT = `Usage: happyskills search [query] [options]
|
|
8
9
|
|
|
9
10
|
Search the skill registry.
|
|
10
11
|
|
|
11
12
|
Arguments:
|
|
12
|
-
query
|
|
13
|
+
query Search term (optional with --mine, --personal, or --workspace)
|
|
13
14
|
|
|
14
15
|
Options:
|
|
15
|
-
--
|
|
16
|
+
-w, --workspace <slug> Search within specific workspace(s) (comma-separated)
|
|
17
|
+
--mine Search across all your workspaces
|
|
18
|
+
--personal Search only your personal workspace
|
|
19
|
+
--tags <tags> Filter by tags (comma-separated)
|
|
20
|
+
--json Output as JSON
|
|
16
21
|
|
|
17
22
|
Aliases: s
|
|
18
23
|
|
|
19
24
|
Examples:
|
|
20
25
|
happyskills search deploy
|
|
21
|
-
happyskills
|
|
26
|
+
happyskills search --mine
|
|
27
|
+
happyskills search deploy --workspace acme
|
|
28
|
+
happyskills s --personal --json`
|
|
22
29
|
|
|
23
30
|
const run = (args) => catch_errors('Search failed', async () => {
|
|
24
31
|
if (args.flags._show_help) {
|
|
@@ -26,32 +33,52 @@ const run = (args) => catch_errors('Search failed', async () => {
|
|
|
26
33
|
return process.exit(EXIT_CODES.SUCCESS)
|
|
27
34
|
}
|
|
28
35
|
|
|
29
|
-
const query = args._.join(' ')
|
|
30
|
-
|
|
31
|
-
|
|
36
|
+
const query = args._.join(' ') || null
|
|
37
|
+
const { mine, personal, workspace, tags } = args.flags
|
|
38
|
+
const has_scope_flag = mine || personal || workspace
|
|
39
|
+
|
|
40
|
+
if (!query && !has_scope_flag && !tags) {
|
|
41
|
+
throw new UsageError('Please provide a search query or use --mine, --personal, or --workspace.')
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const scope = mine ? 'mine' : personal ? 'personal' : undefined
|
|
45
|
+
|
|
46
|
+
if (has_scope_flag) {
|
|
47
|
+
const [, token_data] = await load_token()
|
|
48
|
+
if (!token_data) {
|
|
49
|
+
throw new AuthError('Authentication required. Run `happyskills login` first.')
|
|
50
|
+
}
|
|
32
51
|
}
|
|
33
52
|
|
|
34
|
-
const
|
|
53
|
+
const options = {}
|
|
54
|
+
if (scope) options.scope = scope
|
|
55
|
+
if (workspace) options.workspace = workspace
|
|
56
|
+
if (tags) options.tags = tags
|
|
57
|
+
|
|
58
|
+
const [errors, results] = await repos_api.search(query, options)
|
|
35
59
|
if (errors) throw e('Search failed', errors)
|
|
36
60
|
|
|
37
61
|
const items = Array.isArray(results) ? results : (results?.repos || results?.items || [])
|
|
62
|
+
const effective_scope = scope || (has_scope_flag ? undefined : undefined)
|
|
38
63
|
|
|
39
64
|
if (items.length === 0) {
|
|
40
65
|
if (args.flags.json) {
|
|
41
|
-
print_json({ data: { query, results: [], count: 0 } })
|
|
66
|
+
print_json({ data: { query, scope: effective_scope || 'all', results: [], count: 0 } })
|
|
42
67
|
return
|
|
43
68
|
}
|
|
44
|
-
|
|
69
|
+
const context = query ? ` for "${query}"` : ''
|
|
70
|
+
print_info(`No skills found${context}.`)
|
|
45
71
|
return
|
|
46
72
|
}
|
|
47
73
|
|
|
48
74
|
if (args.flags.json) {
|
|
49
|
-
const
|
|
75
|
+
const mapped = items.map(item => ({
|
|
50
76
|
skill: `${item.owner || item.workspace_slug}/${item.name}`,
|
|
51
77
|
description: item.description || '',
|
|
52
|
-
version: item.latest_version || item.version || '-'
|
|
78
|
+
version: item.latest_version || item.version || '-',
|
|
79
|
+
visibility: item.visibility || 'public'
|
|
53
80
|
}))
|
|
54
|
-
print_json({ data: { query, results, count:
|
|
81
|
+
print_json({ data: { query, scope: effective_scope || 'all', results: mapped, count: mapped.length } })
|
|
55
82
|
return
|
|
56
83
|
}
|
|
57
84
|
|
package/src/index.js
CHANGED
|
@@ -62,6 +62,7 @@ const parse_args = (argv) => {
|
|
|
62
62
|
const SHORT_FLAGS = {
|
|
63
63
|
g: 'global',
|
|
64
64
|
y: 'yes',
|
|
65
|
+
w: 'workspace',
|
|
65
66
|
}
|
|
66
67
|
|
|
67
68
|
const normalize_flags = (flags) => {
|
|
@@ -114,7 +115,7 @@ const run = (argv) => {
|
|
|
114
115
|
set_json_mode()
|
|
115
116
|
}
|
|
116
117
|
|
|
117
|
-
if (args.flags.version) {
|
|
118
|
+
if (args.flags.version === true) {
|
|
118
119
|
show_version()
|
|
119
120
|
return process.exit(EXIT_CODES.SUCCESS)
|
|
120
121
|
}
|