shelfware 0.1.1 → 0.1.3
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/{BZuUK3cr.js → Bzmuhhqm.js} +1 -1
- package/.output/public/_nuxt/{6IstVYrV.js → CJmYvhe6.js} +1 -1
- package/.output/public/_nuxt/CMk5jOCS.js +1 -0
- package/.output/public/_nuxt/DJQqUjJu.js +2 -0
- package/.output/public/_nuxt/DfJzPSBi.js +1 -0
- package/.output/public/_nuxt/EBA28doP.js +14 -0
- package/.output/public/_nuxt/builds/latest.json +1 -1
- package/.output/public/_nuxt/builds/meta/87d7ca63-ebd6-4c54-a087-e23ce5bed209.json +1 -0
- package/.output/public/_nuxt/entry.CKteCcdN.css +2 -0
- package/.output/public/_nuxt/error-404.1qC5mjOI.css +1 -0
- package/.output/public/_nuxt/error-500.BcOmuN7K.css +1 -0
- package/.output/public/_nuxt/rWsrwf2L.js +36 -0
- package/.output/public/_nuxt/{C_VgD9ZI.js → sK9Xse3o.js} +1 -1
- package/.output/server/chunks/_/catalog.mjs +33 -8
- package/.output/server/chunks/nitro/nitro.mjs +96 -96
- package/.output/server/chunks/routes/renderer.mjs +1 -1
- package/.output/server/chunks/virtual/precomputed.mjs +1 -1
- package/.output/server/package.json +1 -1
- package/README.md +18 -4
- package/bin/launch.d.mts +54 -0
- package/bin/launch.mjs +46 -17
- package/package.json +7 -3
- package/.output/public/_nuxt/9C5WUlLE.js +0 -36
- package/.output/public/_nuxt/BXXtFil5.js +0 -1
- package/.output/public/_nuxt/BeLyMmd4.js +0 -1
- package/.output/public/_nuxt/DB5Vxbte.js +0 -2
- package/.output/public/_nuxt/DMg0j7gv.js +0 -14
- package/.output/public/_nuxt/builds/meta/e0d2b6a3-3a30-44b7-8c79-a56f66400005.json +0 -1
- package/.output/public/_nuxt/entry.b3_DJTEt.css +0 -2
- package/.output/public/_nuxt/error-404.CHsn7XF-.css +0 -1
- package/.output/public/_nuxt/error-500.Bawx2bLr.css +0 -1
package/bin/launch.mjs
CHANGED
|
@@ -11,6 +11,9 @@ export const DEFAULT_PORT = 3781
|
|
|
11
11
|
export const PORT_ATTEMPTS = 20
|
|
12
12
|
export const HOST = '127.0.0.1'
|
|
13
13
|
|
|
14
|
+
/** Spec §10.3: the env var the server reads the session token from. */
|
|
15
|
+
export const TOKEN_ENV = 'NUXT_PUBLIC_SHELFWARE_TOKEN'
|
|
16
|
+
|
|
14
17
|
export const HELP = `shelfware — a local catalog of the AI-agent skills on this machine
|
|
15
18
|
|
|
16
19
|
Usage: shelfware [--port <n>] [--no-open] [--help]
|
|
@@ -28,22 +31,27 @@ export function makeToken() {
|
|
|
28
31
|
|
|
29
32
|
/**
|
|
30
33
|
* The raw text stays in the message so `--port abc` reads back as "abc", not NaN.
|
|
31
|
-
*
|
|
32
|
-
* none of which the help text (`--port <n>`) promises.
|
|
34
|
+
* Decimal digits without a leading zero: `Number()` would otherwise accept `0x10`,
|
|
35
|
+
* `1e3`, `' 4000 '` and `065535`, none of which the help text (`--port <n>`) promises.
|
|
33
36
|
*/
|
|
34
37
|
function parsePort(raw) {
|
|
35
|
-
if (
|
|
38
|
+
if (!/^[1-9]\d*$/.test(raw)) throw new Error(`Invalid port: ${JSON.stringify(raw)}`)
|
|
36
39
|
const port = Number(raw)
|
|
37
|
-
if (port
|
|
40
|
+
if (port > 65535) throw new Error(`Invalid port: ${JSON.stringify(raw)}`)
|
|
38
41
|
return port
|
|
39
42
|
}
|
|
40
43
|
|
|
41
44
|
export function parseArgs(argv = [], env = {}) {
|
|
42
45
|
let port = null
|
|
43
46
|
let open = env.SHELFWARE_NO_OPEN !== '1'
|
|
47
|
+
let optionsEnded = false
|
|
44
48
|
for (let i = 0; i < argv.length; i += 1) {
|
|
45
49
|
const arg = argv[i]
|
|
46
|
-
if (
|
|
50
|
+
if (optionsEnded || !arg.startsWith('-')) {
|
|
51
|
+
throw new Error(`Unexpected argument: ${arg}`)
|
|
52
|
+
} else if (arg === '--') {
|
|
53
|
+
optionsEnded = true
|
|
54
|
+
} else if (arg === '--no-open') {
|
|
47
55
|
open = false
|
|
48
56
|
} else if (arg === '--port') {
|
|
49
57
|
if (i + 1 >= argv.length) throw new Error('--port needs a value')
|
|
@@ -57,19 +65,32 @@ export function parseArgs(argv = [], env = {}) {
|
|
|
57
65
|
throw new Error(`Unknown option: ${arg}`)
|
|
58
66
|
}
|
|
59
67
|
}
|
|
60
|
-
|
|
68
|
+
// Unset or empty means unset; anything else must be a real port.
|
|
69
|
+
if (port === null && env.PORT !== undefined && env.PORT !== '') port = parsePort(env.PORT)
|
|
61
70
|
return { help: false, port, open }
|
|
62
71
|
}
|
|
63
72
|
|
|
64
|
-
|
|
73
|
+
/** Why `port` cannot be bound, in words a user can act on. */
|
|
74
|
+
export function portReason(code) {
|
|
75
|
+
if (code === 'EADDRINUSE') return 'is already in use'
|
|
76
|
+
if (code === 'EACCES') return 'needs elevated privileges (EACCES)'
|
|
77
|
+
return `cannot be bound (${code})`
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** Resolves null when `port` can be bound on `host`, otherwise the reason (see `portReason`). */
|
|
81
|
+
export function checkPort(port, host = HOST) {
|
|
65
82
|
return new Promise((resolve) => {
|
|
66
83
|
const server = net.createServer()
|
|
67
84
|
server.unref()
|
|
68
|
-
server.once('error',
|
|
69
|
-
server.listen({ port, host }, () => server.close(() => resolve(
|
|
85
|
+
server.once('error', err => resolve(portReason(err.code ?? 'UNKNOWN')))
|
|
86
|
+
server.listen({ port, host }, () => server.close(() => resolve(null)))
|
|
70
87
|
})
|
|
71
88
|
}
|
|
72
89
|
|
|
90
|
+
export async function isPortFree(port, host = HOST) {
|
|
91
|
+
return (await checkPort(port, host)) === null
|
|
92
|
+
}
|
|
93
|
+
|
|
73
94
|
export async function pickPort(start = DEFAULT_PORT, { attempts = PORT_ATTEMPTS, host = HOST } = {}) {
|
|
74
95
|
for (let port = start; port < start + attempts; port += 1) {
|
|
75
96
|
if (await isPortFree(port, host)) return port
|
|
@@ -132,7 +153,7 @@ export function openBrowser(url, { platform = process.platform, spawn = spawnPro
|
|
|
132
153
|
const args = platform === 'win32' ? ['/c', 'start', '', url] : [url]
|
|
133
154
|
// The opener execs the browser; strip the session token so it never sits in the
|
|
134
155
|
// browser's environment (the served page is the only place it belongs).
|
|
135
|
-
const {
|
|
156
|
+
const { [TOKEN_ENV]: _token, ...childEnv } = env
|
|
136
157
|
// A missing browser opener (e.g. no xdg-open on a headless box) must never take
|
|
137
158
|
// the running server down with it: spawn can throw synchronously (rare) or emit
|
|
138
159
|
// an async 'error' (the common ENOENT case), and an unhandled 'error' on an
|
|
@@ -151,6 +172,11 @@ export function serverEntry(binDir = path.dirname(fileURLToPath(import.meta.url)
|
|
|
151
172
|
return path.resolve(binDir, '..', '.output', 'server', 'index.mjs')
|
|
152
173
|
}
|
|
153
174
|
|
|
175
|
+
/** One line for the user whatever was thrown: Error, string, or nothing at all. */
|
|
176
|
+
export function describeError(err) {
|
|
177
|
+
return err instanceof Error ? err.message : String(err)
|
|
178
|
+
}
|
|
179
|
+
|
|
154
180
|
/**
|
|
155
181
|
* Orchestration with injectable I/O so it can be unit-tested end to end:
|
|
156
182
|
* flags → port → env → import the prebuilt server → wait for health → open.
|
|
@@ -168,7 +194,7 @@ export async function launch({
|
|
|
168
194
|
try {
|
|
169
195
|
args = parseArgs(argv, env)
|
|
170
196
|
} catch (err) {
|
|
171
|
-
log(`shelfware: ${err
|
|
197
|
+
log(`shelfware: ${describeError(err)}`)
|
|
172
198
|
log(HELP)
|
|
173
199
|
exit(1)
|
|
174
200
|
return null
|
|
@@ -182,10 +208,13 @@ export async function launch({
|
|
|
182
208
|
exit(1)
|
|
183
209
|
return null
|
|
184
210
|
}
|
|
185
|
-
if (args.port !== null
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
211
|
+
if (args.port !== null) {
|
|
212
|
+
const problem = await checkPort(args.port)
|
|
213
|
+
if (problem) {
|
|
214
|
+
log(`shelfware: port ${args.port} ${problem} on ${HOST}; pick another (--port or PORT)`)
|
|
215
|
+
exit(1)
|
|
216
|
+
return null
|
|
217
|
+
}
|
|
189
218
|
}
|
|
190
219
|
// Both of these can fail for reasons the user can act on — every port in the
|
|
191
220
|
// range is taken, or the server throws at import (a Nitro crash, or an
|
|
@@ -194,13 +223,13 @@ export async function launch({
|
|
|
194
223
|
let port
|
|
195
224
|
try {
|
|
196
225
|
port = args.port ?? await pickPort(DEFAULT_PORT)
|
|
197
|
-
env
|
|
226
|
+
env[TOKEN_ENV] = makeToken()
|
|
198
227
|
env.NITRO_HOST = HOST
|
|
199
228
|
env.NITRO_PORT = String(port)
|
|
200
229
|
env.NODE_ENV = 'production'
|
|
201
230
|
await importServer()
|
|
202
231
|
} catch (err) {
|
|
203
|
-
log(`shelfware: ${err
|
|
232
|
+
log(`shelfware: ${describeError(err)}`)
|
|
204
233
|
exit(1)
|
|
205
234
|
return null
|
|
206
235
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "shelfware",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "shelfware finds the shelfware in your skill drawers.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"skills",
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"dev": "nuxt dev",
|
|
38
38
|
"build": "nuxt build",
|
|
39
39
|
"prepare": "nuxt prepare",
|
|
40
|
-
"typecheck": "nuxt typecheck",
|
|
40
|
+
"typecheck": "nuxt typecheck && tsc -p tsconfig.unit.json",
|
|
41
41
|
"test": "vitest run",
|
|
42
42
|
"test:unit": "vitest run --project unit --passWithNoTests",
|
|
43
43
|
"test:nuxt": "vitest run --project nuxt --passWithNoTests",
|
|
@@ -51,6 +51,7 @@
|
|
|
51
51
|
"@nuxt/test-utils": "^4.2.0",
|
|
52
52
|
"@nuxt/ui": "^4.11.0",
|
|
53
53
|
"@types/markdown-it": "^14.2.0",
|
|
54
|
+
"@types/node": "^20.19.43",
|
|
54
55
|
"@vue/test-utils": "^2.5.0",
|
|
55
56
|
"@vueuse/core": "^14.4.0",
|
|
56
57
|
"happy-dom": "^20.14.0",
|
|
@@ -63,6 +64,9 @@
|
|
|
63
64
|
"yaml": "^2.9.0"
|
|
64
65
|
},
|
|
65
66
|
"pnpm": {
|
|
66
|
-
"onlyBuiltDependencies": [
|
|
67
|
+
"onlyBuiltDependencies": [
|
|
68
|
+
"esbuild",
|
|
69
|
+
"vue-demi"
|
|
70
|
+
]
|
|
67
71
|
}
|
|
68
72
|
}
|