happyskills 0.28.0 → 0.29.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,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/).
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.29.0] - 2026-04-02
11
+
12
+ ### Added
13
+ - Handle async archive processing for large skill publishes — when the server delegates to a background worker, the CLI polls for completion with a "Processing on server..." spinner
14
+
15
+ ## [0.28.1] - 2026-04-02
16
+
17
+ ### Fixed
18
+ - Exclude macOS metadata files (`._*`, `.DS_Store`) from tar archive during `publish` to prevent server-side extraction failures
19
+
10
20
  ## [0.28.0] - 2026-04-02
11
21
 
12
22
  ### Added
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "happyskills",
3
- "version": "0.28.0",
3
+ "version": "0.29.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/push.js CHANGED
@@ -47,6 +47,15 @@ const archive_push = (owner, repo, { version, message, files, visibility, base_c
47
47
  const [complete_err, complete_data] = await complete_upload(owner, repo, complete_body)
48
48
  if (complete_err) throw e('Upload completion failed', complete_err)
49
49
 
50
+ // Async processing (202) — poll for completion
51
+ if (complete_data.status === 'processing') {
52
+ if (on_progress) on_progress('polling')
53
+ const { poll_push_status } = require('./upload')
54
+ const [poll_err, result] = await poll_push_status(owner, repo, complete_data.upload_id)
55
+ if (poll_err) throw e('Async push processing failed', poll_err)
56
+ return result
57
+ }
58
+
50
59
  return complete_data
51
60
  } finally {
52
61
  cleanup_archive(archive.path)
package/src/api/upload.js CHANGED
@@ -45,4 +45,21 @@ const upload_files_parallel = (files_with_urls, on_progress) => catch_errors('Pa
45
45
  }
46
46
  })
47
47
 
48
- module.exports = { initiate_upload, complete_upload, upload_file_to_s3, upload_files_parallel }
48
+ const poll_push_status = (owner, repo, upload_id, { timeout_ms = 300000, interval_ms = 2000 } = {}) =>
49
+ catch_errors('Push status polling failed', async () => {
50
+ const start = Date.now()
51
+ while (Date.now() - start < timeout_ms) {
52
+ const [err, data] = await client.get(`/repos/${owner}/${repo}/push/status/${upload_id}`)
53
+ if (err) throw e('Failed to check push status', err)
54
+
55
+ if (data.status === 'completed') return data
56
+ if (data.status === 'failed') {
57
+ throw Object.assign(new Error(data.error || 'Push processing failed'), { code: data.code })
58
+ }
59
+
60
+ await new Promise(resolve => setTimeout(resolve, interval_ms))
61
+ }
62
+ throw new Error('Push processing timed out after 5 minutes')
63
+ })
64
+
65
+ module.exports = { initiate_upload, complete_upload, upload_file_to_s3, upload_files_parallel, poll_push_status }
@@ -186,7 +186,11 @@ const run = (args) => catch_errors('Publish failed', async () => {
186
186
 
187
187
  spinner.update(`Publishing ${workspace.slug}/${manifest.name}@${manifest.version}...`)
188
188
  const on_progress = (completed, total) => {
189
- spinner.update(`Uploading files (${completed}/${total})...`)
189
+ if (completed === 'polling') {
190
+ spinner.update('Processing on server...')
191
+ } else {
192
+ spinner.update(`Uploading files (${completed}/${total})...`)
193
+ }
190
194
  }
191
195
  const visibility = args.flags.public ? 'public' : 'private'
192
196
  const push_options = {
@@ -11,7 +11,7 @@ const exec_file = promisify(execFile)
11
11
  const create_archive = (source_dir) =>
12
12
  catch_errors('Failed to create archive', async () => {
13
13
  const archive_path = path.join(os.tmpdir(), `happyskills-${crypto.randomUUID()}.tar.gz`)
14
- await exec_file('tar', ['czf', archive_path, '-C', source_dir, '.'])
14
+ await exec_file('tar', ['czf', archive_path, '--exclude', '._*', '--exclude', '.DS_Store', '-C', source_dir, '.'])
15
15
  const buffer = await fs.promises.readFile(archive_path)
16
16
  const sha = crypto.createHash('sha256').update(buffer).digest('hex')
17
17
  return { path: archive_path, buffer, sha, size: buffer.length }