shelfware 0.1.0 → 0.1.1
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/.output/nitro.json +1 -1
- package/.output/public/_nuxt/builds/latest.json +1 -1
- package/.output/public/_nuxt/builds/meta/e0d2b6a3-3a30-44b7-8c79-a56f66400005.json +1 -0
- package/.output/server/chunks/nitro/nitro.mjs +45 -45
- package/.output/server/package.json +1 -1
- package/README.md +1 -1
- package/bin/launch.mjs +78 -20
- package/bin/shelfware.mjs +8 -3
- package/package.json +1 -1
- package/.output/public/_nuxt/builds/meta/4c307968-4837-497b-a570-d6ee336accc4.json +0 -1
package/.output/nitro.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"id":"
|
|
1
|
+
{"id":"e0d2b6a3-3a30-44b7-8c79-a56f66400005","timestamp":1788705987913}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"id":"e0d2b6a3-3a30-44b7-8c79-a56f66400005","timestamp":1788705987913,"prerendered":[]}
|
|
@@ -4056,7 +4056,7 @@ function _expandFromEnv(value) {
|
|
|
4056
4056
|
const _inlineRuntimeConfig = {
|
|
4057
4057
|
"app": {
|
|
4058
4058
|
"baseURL": "/",
|
|
4059
|
-
"buildId": "
|
|
4059
|
+
"buildId": "e0d2b6a3-3a30-44b7-8c79-a56f66400005",
|
|
4060
4060
|
"buildAssetsDir": "/_nuxt/",
|
|
4061
4061
|
"cdnURL": ""
|
|
4062
4062
|
},
|
|
@@ -4574,107 +4574,107 @@ const assets = {
|
|
|
4574
4574
|
"/_nuxt/6IstVYrV.js": {
|
|
4575
4575
|
"type": "text/javascript; charset=utf-8",
|
|
4576
4576
|
"etag": "\"e90-QSo0QKAvkvHEus5Dhcj+CyKCqD4\"",
|
|
4577
|
-
"mtime": "2026-09-
|
|
4577
|
+
"mtime": "2026-09-06T14:46:29.734Z",
|
|
4578
4578
|
"size": 3728,
|
|
4579
4579
|
"path": "../public/_nuxt/6IstVYrV.js"
|
|
4580
4580
|
},
|
|
4581
4581
|
"/_nuxt/BDNMzG2s.js": {
|
|
4582
4582
|
"type": "text/javascript; charset=utf-8",
|
|
4583
4583
|
"etag": "\"54-MasMfSk/A98C3Gn9uIOxtFxkWNw\"",
|
|
4584
|
-
"mtime": "2026-09-
|
|
4584
|
+
"mtime": "2026-09-06T14:46:29.734Z",
|
|
4585
4585
|
"size": 84,
|
|
4586
4586
|
"path": "../public/_nuxt/BDNMzG2s.js"
|
|
4587
4587
|
},
|
|
4588
|
-
"/_nuxt/BXXtFil5.js": {
|
|
4589
|
-
"type": "text/javascript; charset=utf-8",
|
|
4590
|
-
"etag": "\"1bd2-NDdun38lYj0OD2wayMdetDX3ehc\"",
|
|
4591
|
-
"mtime": "2026-09-06T10:58:49.045Z",
|
|
4592
|
-
"size": 7122,
|
|
4593
|
-
"path": "../public/_nuxt/BXXtFil5.js"
|
|
4594
|
-
},
|
|
4595
|
-
"/_nuxt/BZuUK3cr.js": {
|
|
4596
|
-
"type": "text/javascript; charset=utf-8",
|
|
4597
|
-
"etag": "\"d4e-rtWL1k144ZrYuBUrIE7FN7q4gDI\"",
|
|
4598
|
-
"mtime": "2026-09-06T10:58:49.045Z",
|
|
4599
|
-
"size": 3406,
|
|
4600
|
-
"path": "../public/_nuxt/BZuUK3cr.js"
|
|
4601
|
-
},
|
|
4602
4588
|
"/_nuxt/BW3xJdEx.js": {
|
|
4603
4589
|
"type": "text/javascript; charset=utf-8",
|
|
4604
4590
|
"etag": "\"2da-zDmvCG/tLW/peRiDRHBkm+3Edw4\"",
|
|
4605
|
-
"mtime": "2026-09-
|
|
4591
|
+
"mtime": "2026-09-06T14:46:29.735Z",
|
|
4606
4592
|
"size": 730,
|
|
4607
4593
|
"path": "../public/_nuxt/BW3xJdEx.js"
|
|
4608
4594
|
},
|
|
4595
|
+
"/_nuxt/BXXtFil5.js": {
|
|
4596
|
+
"type": "text/javascript; charset=utf-8",
|
|
4597
|
+
"etag": "\"1bd2-NDdun38lYj0OD2wayMdetDX3ehc\"",
|
|
4598
|
+
"mtime": "2026-09-06T14:46:29.735Z",
|
|
4599
|
+
"size": 7122,
|
|
4600
|
+
"path": "../public/_nuxt/BXXtFil5.js"
|
|
4601
|
+
},
|
|
4609
4602
|
"/_nuxt/BeLyMmd4.js": {
|
|
4610
4603
|
"type": "text/javascript; charset=utf-8",
|
|
4611
4604
|
"etag": "\"1c1-HX03BAMzK3TbabTUt+zazZac6jY\"",
|
|
4612
|
-
"mtime": "2026-09-
|
|
4605
|
+
"mtime": "2026-09-06T14:46:29.735Z",
|
|
4613
4606
|
"size": 449,
|
|
4614
4607
|
"path": "../public/_nuxt/BeLyMmd4.js"
|
|
4615
4608
|
},
|
|
4616
|
-
"/_nuxt/
|
|
4609
|
+
"/_nuxt/BZuUK3cr.js": {
|
|
4617
4610
|
"type": "text/javascript; charset=utf-8",
|
|
4618
|
-
"etag": "\"
|
|
4619
|
-
"mtime": "2026-09-
|
|
4620
|
-
"size":
|
|
4621
|
-
"path": "../public/_nuxt/
|
|
4611
|
+
"etag": "\"d4e-rtWL1k144ZrYuBUrIE7FN7q4gDI\"",
|
|
4612
|
+
"mtime": "2026-09-06T14:46:29.735Z",
|
|
4613
|
+
"size": 3406,
|
|
4614
|
+
"path": "../public/_nuxt/BZuUK3cr.js"
|
|
4622
4615
|
},
|
|
4623
4616
|
"/_nuxt/C_VgD9ZI.js": {
|
|
4624
4617
|
"type": "text/javascript; charset=utf-8",
|
|
4625
4618
|
"etag": "\"16e9b-CaBNJIMBZbSuSxfSRmsiBioAcZc\"",
|
|
4626
|
-
"mtime": "2026-09-
|
|
4619
|
+
"mtime": "2026-09-06T14:46:29.735Z",
|
|
4627
4620
|
"size": 93851,
|
|
4628
4621
|
"path": "../public/_nuxt/C_VgD9ZI.js"
|
|
4629
4622
|
},
|
|
4623
|
+
"/_nuxt/DB5Vxbte.js": {
|
|
4624
|
+
"type": "text/javascript; charset=utf-8",
|
|
4625
|
+
"etag": "\"9f1f-lZvm375DqcFM+vOcKc2rvEVowVM\"",
|
|
4626
|
+
"mtime": "2026-09-06T14:46:29.735Z",
|
|
4627
|
+
"size": 40735,
|
|
4628
|
+
"path": "../public/_nuxt/DB5Vxbte.js"
|
|
4629
|
+
},
|
|
4630
4630
|
"/_nuxt/error-404.CHsn7XF-.css": {
|
|
4631
4631
|
"type": "text/css; charset=utf-8",
|
|
4632
4632
|
"etag": "\"97d-2Jv1I5SVRWWiHcs7d0fjdFylhP4\"",
|
|
4633
|
-
"mtime": "2026-09-
|
|
4633
|
+
"mtime": "2026-09-06T14:46:29.735Z",
|
|
4634
4634
|
"size": 2429,
|
|
4635
4635
|
"path": "../public/_nuxt/error-404.CHsn7XF-.css"
|
|
4636
4636
|
},
|
|
4637
4637
|
"/_nuxt/error-500.Bawx2bLr.css": {
|
|
4638
4638
|
"type": "text/css; charset=utf-8",
|
|
4639
4639
|
"etag": "\"772-1WbEnJKsOodm+94L4M1TNGEMGdA\"",
|
|
4640
|
-
"mtime": "2026-09-
|
|
4640
|
+
"mtime": "2026-09-06T14:46:29.735Z",
|
|
4641
4641
|
"size": 1906,
|
|
4642
4642
|
"path": "../public/_nuxt/error-500.Bawx2bLr.css"
|
|
4643
4643
|
},
|
|
4644
|
+
"/_nuxt/9C5WUlLE.js": {
|
|
4645
|
+
"type": "text/javascript; charset=utf-8",
|
|
4646
|
+
"etag": "\"76057-x5s6sLzHAii+nwAfxpSA3vSHenw\"",
|
|
4647
|
+
"mtime": "2026-09-06T14:46:29.735Z",
|
|
4648
|
+
"size": 483415,
|
|
4649
|
+
"path": "../public/_nuxt/9C5WUlLE.js"
|
|
4650
|
+
},
|
|
4644
4651
|
"/_nuxt/builds/latest.json": {
|
|
4645
4652
|
"type": "application/json",
|
|
4646
|
-
"etag": "\"47-
|
|
4647
|
-
"mtime": "2026-09-
|
|
4653
|
+
"etag": "\"47-gnWfjO3atXpuKHcTeevtRdg7IRg\"",
|
|
4654
|
+
"mtime": "2026-09-06T14:46:29.732Z",
|
|
4648
4655
|
"size": 71,
|
|
4649
4656
|
"path": "../public/_nuxt/builds/latest.json"
|
|
4650
4657
|
},
|
|
4651
4658
|
"/_nuxt/entry.b3_DJTEt.css": {
|
|
4652
4659
|
"type": "text/css; charset=utf-8",
|
|
4653
4660
|
"etag": "\"2fd30-a1fPhKsI3m5s3WkUz/aQvIKj6Ow\"",
|
|
4654
|
-
"mtime": "2026-09-
|
|
4661
|
+
"mtime": "2026-09-06T14:46:29.735Z",
|
|
4655
4662
|
"size": 195888,
|
|
4656
4663
|
"path": "../public/_nuxt/entry.b3_DJTEt.css"
|
|
4657
4664
|
},
|
|
4665
|
+
"/_nuxt/builds/meta/e0d2b6a3-3a30-44b7-8c79-a56f66400005.json": {
|
|
4666
|
+
"type": "application/json",
|
|
4667
|
+
"etag": "\"58-/O8b6cy7JVz90KrikBRsiFQaWdI\"",
|
|
4668
|
+
"mtime": "2026-09-06T14:46:29.731Z",
|
|
4669
|
+
"size": 88,
|
|
4670
|
+
"path": "../public/_nuxt/builds/meta/e0d2b6a3-3a30-44b7-8c79-a56f66400005.json"
|
|
4671
|
+
},
|
|
4658
4672
|
"/_nuxt/DMg0j7gv.js": {
|
|
4659
4673
|
"type": "text/javascript; charset=utf-8",
|
|
4660
4674
|
"etag": "\"25529-MrmjBHZKW5MJ6UC+MAR50nTTq7U\"",
|
|
4661
|
-
"mtime": "2026-09-
|
|
4675
|
+
"mtime": "2026-09-06T14:46:29.735Z",
|
|
4662
4676
|
"size": 152873,
|
|
4663
4677
|
"path": "../public/_nuxt/DMg0j7gv.js"
|
|
4664
|
-
},
|
|
4665
|
-
"/_nuxt/DB5Vxbte.js": {
|
|
4666
|
-
"type": "text/javascript; charset=utf-8",
|
|
4667
|
-
"etag": "\"9f1f-lZvm375DqcFM+vOcKc2rvEVowVM\"",
|
|
4668
|
-
"mtime": "2026-09-06T10:58:49.045Z",
|
|
4669
|
-
"size": 40735,
|
|
4670
|
-
"path": "../public/_nuxt/DB5Vxbte.js"
|
|
4671
|
-
},
|
|
4672
|
-
"/_nuxt/builds/meta/4c307968-4837-497b-a570-d6ee336accc4.json": {
|
|
4673
|
-
"type": "application/json",
|
|
4674
|
-
"etag": "\"58-vdwrkuxkvo48U14qzidl95rF2gw\"",
|
|
4675
|
-
"mtime": "2026-09-06T10:58:49.040Z",
|
|
4676
|
-
"size": 88,
|
|
4677
|
-
"path": "../public/_nuxt/builds/meta/4c307968-4837-497b-a570-d6ee336accc4.json"
|
|
4678
4678
|
}
|
|
4679
4679
|
};
|
|
4680
4680
|
|
package/README.md
CHANGED
|
@@ -24,7 +24,7 @@ Skills outside your home directory (project-level `.claude/skills` inside a repo
|
|
|
24
24
|
|
|
25
25
|
- **`npx shelfware` prints "coming soon".** Your npx cache still holds the `0.0.1` placeholder. Run `npx shelfware@latest` once; npx then caches the real release.
|
|
26
26
|
- **The browser did not open.** Copy the URL from the `shelfware at http://127.0.0.1:<port>` line, or start with `--no-open` and open it yourself.
|
|
27
|
-
- **Port in use.**
|
|
27
|
+
- **Port in use.** An explicit `--port <n>` that is taken is refused at once with a one-line message; without either the flag or PORT shelfware tries 3781 through 3800. A typo in a flag prints the message and the help text instead of a stack trace.
|
|
28
28
|
- **The catalog is empty.** Check that your skills live under `$HOME/.<tool>/skills`; other locations are not scanned yet.
|
|
29
29
|
|
|
30
30
|
## What you get
|
package/bin/launch.mjs
CHANGED
|
@@ -13,7 +13,7 @@ export const HOST = '127.0.0.1'
|
|
|
13
13
|
|
|
14
14
|
export const HELP = `shelfware — a local catalog of the AI-agent skills on this machine
|
|
15
15
|
|
|
16
|
-
Usage: shelfware [--port <n>] [--no-open]
|
|
16
|
+
Usage: shelfware [--port <n>] [--no-open] [--help]
|
|
17
17
|
|
|
18
18
|
--port <n> bind 127.0.0.1:<n> instead of the first free port from ${DEFAULT_PORT}
|
|
19
19
|
--no-open do not open the browser (also SHELFWARE_NO_OPEN=1)
|
|
@@ -26,6 +26,18 @@ export function makeToken() {
|
|
|
26
26
|
return `sw_${crypto.randomBytes(32).toString('hex')}`
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
+
/**
|
|
30
|
+
* The raw text stays in the message so `--port abc` reads back as "abc", not NaN.
|
|
31
|
+
* Digits only: `Number()` would otherwise accept `0x10`, `1e3` and `' 4000 '`,
|
|
32
|
+
* none of which the help text (`--port <n>`) promises.
|
|
33
|
+
*/
|
|
34
|
+
function parsePort(raw) {
|
|
35
|
+
if (!/^\d+$/.test(raw)) throw new Error(`Invalid port: ${JSON.stringify(raw)}`)
|
|
36
|
+
const port = Number(raw)
|
|
37
|
+
if (port < 1 || port > 65535) throw new Error(`Invalid port: ${JSON.stringify(raw)}`)
|
|
38
|
+
return port
|
|
39
|
+
}
|
|
40
|
+
|
|
29
41
|
export function parseArgs(argv = [], env = {}) {
|
|
30
42
|
let port = null
|
|
31
43
|
let open = env.SHELFWARE_NO_OPEN !== '1'
|
|
@@ -34,18 +46,18 @@ export function parseArgs(argv = [], env = {}) {
|
|
|
34
46
|
if (arg === '--no-open') {
|
|
35
47
|
open = false
|
|
36
48
|
} else if (arg === '--port') {
|
|
37
|
-
|
|
49
|
+
if (i + 1 >= argv.length) throw new Error('--port needs a value')
|
|
50
|
+
port = parsePort(argv[i + 1])
|
|
38
51
|
i += 1
|
|
39
52
|
} else if (arg.startsWith('--port=')) {
|
|
40
|
-
port =
|
|
53
|
+
port = parsePort(arg.slice('--port='.length))
|
|
41
54
|
} else if (arg === '--help' || arg === '-h') {
|
|
42
55
|
return { help: true, port: null, open }
|
|
56
|
+
} else {
|
|
57
|
+
throw new Error(`Unknown option: ${arg}`)
|
|
43
58
|
}
|
|
44
59
|
}
|
|
45
|
-
if (port === null && env.PORT) port =
|
|
46
|
-
if (port !== null && (!Number.isInteger(port) || port < 1 || port > 65535)) {
|
|
47
|
-
throw new Error(`Invalid port: ${port}`)
|
|
48
|
-
}
|
|
60
|
+
if (port === null && env.PORT) port = parsePort(env.PORT)
|
|
49
61
|
return { help: false, port, open }
|
|
50
62
|
}
|
|
51
63
|
|
|
@@ -87,24 +99,46 @@ export function probeHealth(port, host = HOST) {
|
|
|
87
99
|
})
|
|
88
100
|
}
|
|
89
101
|
|
|
90
|
-
export async function waitForHealth(port, { timeoutMs = 10_000, intervalMs = 100, host = HOST, probe = probeHealth } = {}) {
|
|
102
|
+
export async function waitForHealth(port, { timeoutMs = 10_000, intervalMs = 100, host = HOST, probe = probeHealth, signal } = {}) {
|
|
91
103
|
const deadline = Date.now() + timeoutMs
|
|
92
|
-
while (Date.now() < deadline) {
|
|
104
|
+
while (Date.now() < deadline && !signal?.aborted) {
|
|
93
105
|
if (await probe(port, host)) return true
|
|
94
|
-
await
|
|
106
|
+
await sleep(intervalMs, signal)
|
|
95
107
|
}
|
|
96
108
|
return false
|
|
97
109
|
}
|
|
98
110
|
|
|
99
|
-
|
|
111
|
+
/** Resolves after `ms`, or as soon as `signal` aborts — and then leaves no timer behind. */
|
|
112
|
+
function sleep(ms, signal) {
|
|
113
|
+
return new Promise((resolve) => {
|
|
114
|
+
if (signal?.aborted) {
|
|
115
|
+
resolve()
|
|
116
|
+
return
|
|
117
|
+
}
|
|
118
|
+
const onAbort = () => {
|
|
119
|
+
clearTimeout(timer)
|
|
120
|
+
resolve()
|
|
121
|
+
}
|
|
122
|
+
const timer = setTimeout(() => {
|
|
123
|
+
signal?.removeEventListener('abort', onAbort)
|
|
124
|
+
resolve()
|
|
125
|
+
}, ms)
|
|
126
|
+
signal?.addEventListener('abort', onAbort, { once: true })
|
|
127
|
+
})
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export function openBrowser(url, { platform = process.platform, spawn = spawnProcess, env = process.env } = {}) {
|
|
100
131
|
const cmd = platform === 'darwin' ? 'open' : platform === 'win32' ? 'cmd' : 'xdg-open'
|
|
101
132
|
const args = platform === 'win32' ? ['/c', 'start', '', url] : [url]
|
|
133
|
+
// The opener execs the browser; strip the session token so it never sits in the
|
|
134
|
+
// browser's environment (the served page is the only place it belongs).
|
|
135
|
+
const { NUXT_PUBLIC_SHELFWARE_TOKEN: _token, ...childEnv } = env
|
|
102
136
|
// A missing browser opener (e.g. no xdg-open on a headless box) must never take
|
|
103
137
|
// the running server down with it: spawn can throw synchronously (rare) or emit
|
|
104
138
|
// an async 'error' (the common ENOENT case), and an unhandled 'error' on an
|
|
105
139
|
// EventEmitter is fatal. Swallow both.
|
|
106
140
|
try {
|
|
107
|
-
const child = spawn(cmd, args, { stdio: 'ignore', detached: true })
|
|
141
|
+
const child = spawn(cmd, args, { stdio: 'ignore', detached: true, env: childEnv })
|
|
108
142
|
child.on?.('error', () => {})
|
|
109
143
|
child.unref?.()
|
|
110
144
|
} catch {
|
|
@@ -130,7 +164,15 @@ export async function launch({
|
|
|
130
164
|
entry = serverEntry(),
|
|
131
165
|
spawn = spawnProcess,
|
|
132
166
|
} = {}) {
|
|
133
|
-
|
|
167
|
+
let args
|
|
168
|
+
try {
|
|
169
|
+
args = parseArgs(argv, env)
|
|
170
|
+
} catch (err) {
|
|
171
|
+
log(`shelfware: ${err.message}`)
|
|
172
|
+
log(HELP)
|
|
173
|
+
exit(1)
|
|
174
|
+
return null
|
|
175
|
+
}
|
|
134
176
|
if (args.help) {
|
|
135
177
|
log(HELP)
|
|
136
178
|
return null
|
|
@@ -140,12 +182,28 @@ export async function launch({
|
|
|
140
182
|
exit(1)
|
|
141
183
|
return null
|
|
142
184
|
}
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
185
|
+
if (args.port !== null && !(await isPortFree(args.port))) {
|
|
186
|
+
log(`shelfware: port ${args.port} is already in use on ${HOST}; pick another with --port`)
|
|
187
|
+
exit(1)
|
|
188
|
+
return null
|
|
189
|
+
}
|
|
190
|
+
// Both of these can fail for reasons the user can act on — every port in the
|
|
191
|
+
// range is taken, or the server throws at import (a Nitro crash, or an
|
|
192
|
+
// EADDRINUSE from losing the race for the auto-picked port). One line, exit 1,
|
|
193
|
+
// never a stack trace.
|
|
194
|
+
let port
|
|
195
|
+
try {
|
|
196
|
+
port = args.port ?? await pickPort(DEFAULT_PORT)
|
|
197
|
+
env.NUXT_PUBLIC_SHELFWARE_TOKEN = makeToken()
|
|
198
|
+
env.NITRO_HOST = HOST
|
|
199
|
+
env.NITRO_PORT = String(port)
|
|
200
|
+
env.NODE_ENV = 'production'
|
|
201
|
+
await importServer()
|
|
202
|
+
} catch (err) {
|
|
203
|
+
log(`shelfware: ${err.message}`)
|
|
204
|
+
exit(1)
|
|
205
|
+
return null
|
|
206
|
+
}
|
|
149
207
|
if (!(await waitForHealth(port))) {
|
|
150
208
|
log(`shelfware: the server did not answer on http://${HOST}:${port} within 10 s`)
|
|
151
209
|
exit(1)
|
|
@@ -153,6 +211,6 @@ export async function launch({
|
|
|
153
211
|
}
|
|
154
212
|
const url = `http://${HOST}:${port}`
|
|
155
213
|
log(`shelfware at ${url}`)
|
|
156
|
-
if (args.open) openBrowser(url, { spawn })
|
|
214
|
+
if (args.open) openBrowser(url, { spawn, env })
|
|
157
215
|
return { port, url }
|
|
158
216
|
}
|
package/bin/shelfware.mjs
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { launch } from './launch.mjs'
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
// launch() reports every failure it can foresee as one line and exits 1; this is
|
|
5
|
+
// the last resort, so an unforeseen throw still never reaches the user as a stack.
|
|
6
|
+
try {
|
|
7
|
+
await launch({ importServer: () => import('../.output/server/index.mjs') })
|
|
8
|
+
} catch (err) {
|
|
9
|
+
console.error(`shelfware: ${err instanceof Error ? err.message : String(err)}`)
|
|
10
|
+
process.exit(1)
|
|
11
|
+
}
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"id":"4c307968-4837-497b-a570-d6ee336accc4","timestamp":1788692326920,"prerendered":[]}
|