happyskills 0.33.1 → 0.34.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,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/).
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.34.0] - 2026-04-09
11
+
12
+ ### Changed
13
+ - Make smart search (semantic + quality ranking) the default when a query is provided — `search "query"` now uses hybrid vector + full-text matching instead of keyword-only
14
+
15
+ ### Added
16
+ - Add `--exact` flag to `search` command to opt out of smart search and use keyword-only matching
17
+ - Add `hint` field to `--exact --json` responses nudging users toward smart search for better results
18
+
10
19
  ## [0.33.1] - 2026-04-08
11
20
 
12
21
  ### Fixed
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "happyskills",
3
- "version": "0.33.1",
3
+ "version": "0.34.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)",
@@ -10,6 +10,9 @@ const HELP_TEXT = `Usage: happyskills search [query] [options]
10
10
 
11
11
  Search the skill registry.
12
12
 
13
+ When a query is provided, smart search (semantic + quality ranking) is used by
14
+ default. Use --exact to fall back to keyword-only matching.
15
+
13
16
  Arguments:
14
17
  query Search term (optional with --mine, --personal, or --workspace)
15
18
 
@@ -19,21 +22,22 @@ Options:
19
22
  --personal Search only your personal workspace
20
23
  --tags <tags> Filter by tags (comma-separated)
21
24
  --type <type> Filter by type (skill, kit)
22
- --smart, -S Use semantic search (vector + text + quality ranking)
23
- --limit <n> Max results (default: 10, max: 50) — used with --smart
24
- --min-quality <n> Minimum quality score 0-100 — used with --smart
25
+ --exact Use keyword-only matching instead of smart search
26
+ --limit <n> Max results (default: 10, max: 50)
27
+ --min-quality <n> Minimum quality score 0-100
25
28
  --json Output as JSON
26
29
 
27
30
  Aliases: s
28
31
 
29
32
  Examples:
30
33
  happyskills search deploy
34
+ happyskills search "REST API with Node.js"
31
35
  happyskills search --mine
32
- happyskills search "REST API with Node.js" --smart
33
36
  happyskills search deploy --workspace acme
34
37
  happyskills search --type kit
35
38
  happyskills s --personal --json
36
- happyskills search "deploy to AWS" -S --limit 5`
39
+ happyskills search "deploy to AWS" --limit 5
40
+ happyskills search deploy --exact`
37
41
 
38
42
  const QUALITY_TIERS = [
39
43
  { min: 80, label: 'High quality', color: cyan },
@@ -140,7 +144,7 @@ const run_smart_search = (args, query, options) => catch_errors('Smart search fa
140
144
  console.log(`\n${gray(`Showing ${items.length} result${items.length === 1 ? '' : 's'}. Install with: happyskills install <owner>/<name>`)}\n`)
141
145
  })
142
146
 
143
- const run_keyword_search = (args, query, options) => catch_errors('Keyword search failed', async () => {
147
+ const run_keyword_search = (args, query, options, { is_exact } = {}) => catch_errors('Keyword search failed', async () => {
144
148
  const [errors, results] = await repos_api.search(query, options)
145
149
  if (errors) throw e('Search failed', errors)
146
150
 
@@ -149,7 +153,9 @@ const run_keyword_search = (args, query, options) => catch_errors('Keyword searc
149
153
 
150
154
  if (items.length === 0) {
151
155
  if (args.flags.json) {
152
- print_json({ data: { query, scope: effective_scope, results: [], count: 0 } })
156
+ const data = { query, scope: effective_scope, results: [], count: 0 }
157
+ if (is_exact) data.hint = 'Smart search (default) uses semantic matching for better results. Remove --exact to use it.'
158
+ print_json({ data })
153
159
  return
154
160
  }
155
161
  const context = query ? ` for "${query}"` : ''
@@ -165,7 +171,9 @@ const run_keyword_search = (args, query, options) => catch_errors('Keyword searc
165
171
  version: item.latest_version || item.version || '-',
166
172
  visibility: item.visibility || 'public'
167
173
  }))
168
- print_json({ data: { query, scope: effective_scope, results: mapped, count: mapped.length } })
174
+ const data = { query, scope: effective_scope, results: mapped, count: mapped.length }
175
+ if (is_exact) data.hint = 'Smart search (default) uses semantic matching for better results. Remove --exact to use it.'
176
+ print_json({ data })
169
177
  return
170
178
  }
171
179
 
@@ -191,13 +199,11 @@ const run = (args) => catch_errors('Search failed', async () => {
191
199
 
192
200
  const query = args._.join(' ') || null
193
201
  const { mine, personal, workspace, tags, type } = args.flags
194
- const is_smart = !!(args.flags.smart || args.flags.S)
202
+ const is_exact = !!args.flags.exact
203
+ // --smart / -S accepted for backward compat (now the default when a query is provided)
204
+ const use_smart = query && !is_exact
195
205
  const has_scope_flag = mine || personal || workspace
196
206
 
197
- if (is_smart && !query) {
198
- throw new UsageError('--smart requires a search query.')
199
- }
200
-
201
207
  if (!query && !has_scope_flag && !tags && !type) {
202
208
  throw new UsageError('Please provide a search query or use --mine, --personal, or --workspace.')
203
209
  }
@@ -221,7 +227,7 @@ const run = (args) => catch_errors('Search failed', async () => {
221
227
  if (tags) options.tags = tags
222
228
  if (type) options.type = type
223
229
 
224
- if (is_smart) {
230
+ if (use_smart) {
225
231
  const [errors] = await run_smart_search(args, query, options)
226
232
  if (errors) throw e('Smart search failed', errors)
227
233
  } else {
@@ -231,7 +237,7 @@ const run = (args) => catch_errors('Search failed', async () => {
231
237
  kw_options.workspace = options.workspace_slug
232
238
  delete kw_options.workspace_slug
233
239
  }
234
- const [errors] = await run_keyword_search(args, query, kw_options)
240
+ const [errors] = await run_keyword_search(args, query, kw_options, { is_exact })
235
241
  if (errors) throw e('Search failed', errors)
236
242
  }
237
243
  }).then(([errors]) => { if (errors) { exit_with_error(errors); return } })