happyskills 0.37.0 → 0.38.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,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/).
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.38.0] - 2026-05-01
11
+
12
+ ### Changed
13
+ - **BREAKING:** Make `--limit` required for the `search` command — there is no default. Running `happyskills search ...` without `--limit` now returns a usage error (exit code 2). The previous hidden default of 10 caused AI-agent callers to miss results without realizing a limit was being applied. Affects both smart-search and `--exact` keyword modes. Update help text and all examples to show `--limit` explicitly.
14
+
15
+ ### Fixed
16
+ - Fix `search --workspace <slug>` (in browse mode with no query, and in `--exact` keyword mode) silently excluding private skills the caller has access to. The CLI was omitting the auth token when only `--workspace` was specified without `--mine` or `--personal`, causing the API to fall back to public-only results. The auth token is now always attached when one is available, regardless of the search mode.
17
+
10
18
  ## [0.37.0] - 2026-04-25
11
19
 
12
20
  ### Added
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "happyskills",
3
- "version": "0.37.0",
3
+ "version": "0.38.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/repos.js CHANGED
@@ -10,8 +10,7 @@ const search = (query, options = {}) => catch_errors('Search failed', async () =
10
10
  if (options.workspace) params.set('workspace', options.workspace)
11
11
  if (options.tags) params.set('tags', options.tags)
12
12
  if (options.type) params.set('type', options.type)
13
- const needs_auth = options.scope && options.scope !== 'public'
14
- const [errors, data] = await client.get(`/repos/search?${params}`, { auth: needs_auth || false })
13
+ const [errors, data] = await client.get(`/repos/search?${params}`)
15
14
  if (errors) throw errors[errors.length - 1]
16
15
  return data
17
16
  })
@@ -23,21 +23,21 @@ Options:
23
23
  --tags <tags> Filter by tags (comma-separated)
24
24
  --type <type> Filter by type (skill, kit)
25
25
  --exact Use keyword-only matching instead of smart search
26
- --limit <n> Max results (default: 10, max: 50)
26
+ --limit <n> Max results (required, 1-50)
27
27
  --min-quality <n> Minimum quality score 0-100
28
28
  --json Output as JSON
29
29
 
30
30
  Aliases: s
31
31
 
32
32
  Examples:
33
- happyskills search deploy
34
- happyskills search "REST API with Node.js"
35
- happyskills search --mine
36
- happyskills search deploy --workspace acme
37
- happyskills search --type kit
38
- happyskills s --personal --json
33
+ happyskills search deploy --limit 10
34
+ happyskills search "REST API with Node.js" --limit 10
35
+ happyskills search --mine --limit 20
36
+ happyskills search deploy --workspace acme --limit 50
37
+ happyskills search --type kit --limit 10
38
+ happyskills s --personal --json --limit 20
39
39
  happyskills search "deploy to AWS" --limit 5
40
- happyskills search deploy --exact`
40
+ happyskills search deploy --exact --limit 10`
41
41
 
42
42
  const QUALITY_TIERS = [
43
43
  { min: 80, label: 'High quality', color: cyan },
@@ -95,7 +95,7 @@ const format_smart_result = (item, index) => {
95
95
  }
96
96
 
97
97
  const run_smart_search = (args, query, options) => catch_errors('Smart search failed', async () => {
98
- const limit = args.flags.limit ? parseInt(args.flags.limit) : 10
98
+ const limit = parseInt(args.flags.limit)
99
99
  const capped_limit = Math.min(Math.max(limit, 1), 50)
100
100
  const min_quality = args.flags['min-quality'] != null ? parseInt(args.flags['min-quality']) : null
101
101
 
@@ -230,6 +230,10 @@ const run = (args) => catch_errors('Search failed', async () => {
230
230
  throw new UsageError('Please provide a search query or use --mine, --personal, or --workspace.')
231
231
  }
232
232
 
233
+ if (!args.flags.limit) {
234
+ throw new UsageError('--limit is required. Specify the number of results you want (e.g. --limit 10).')
235
+ }
236
+
233
237
  if (type && !VALID_SKILL_TYPES.includes(type)) {
234
238
  throw new UsageError(`--type must be one of: ${VALID_SKILL_TYPES.join(', ')}`)
235
239
  }
@@ -276,8 +276,9 @@ describe('CLI — --json: stdout is always valid JSON', () => {
276
276
  })
277
277
 
278
278
  it('network error produces JSON with code NETWORK_ERROR', () => {
279
- // search requires a network call; localhost:0 always fails
280
- const { stdout, code } = run(['search', 'anything', '--json'])
279
+ // search requires a network call; localhost:0 always fails.
280
+ // --limit is required, so it must be passed to reach the network call.
281
+ const { stdout, code } = run(['search', 'anything', '--json', '--limit', '10'])
281
282
  assert.strictEqual(code, 4)
282
283
  const out = parse_json_output(stdout, 'search --json network failure')
283
284
  assert.strictEqual(out.error.code, 'NETWORK_ERROR')