@hivemagnus/hive-skills 1.0.3 → 1.1.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/README.md +12 -2
- package/dist/check.js +67 -0
- package/dist/cli.js +19 -2
- package/dist/env-schema.js +25 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,6 +4,14 @@
|
|
|
4
4
|
|
|
5
5
|
The registry is intentionally small and local-first. SQLite records skills, releases, sessions, audit events, and deployment targets; archives live under the configured data directory. The initial catalog entry is **HiveMem**, based on the existing HiveMem Hermes plugin/skill source supplied at seed time.
|
|
6
6
|
|
|
7
|
+
## Install with the Hive launcher
|
|
8
|
+
|
|
9
|
+
Run `npx @hivemagnus/hive@^1.1.0 init --services all` to configure all four services,
|
|
10
|
+
or select `--services skills` for this registry alone. The launcher generates private
|
|
11
|
+
configuration, a strong session secret and TLS, and wires the Router's Skills panel.
|
|
12
|
+
Skills OS/PAM administration remains separate from IAM sign-in. See the
|
|
13
|
+
[launcher guide](../../packages/hive/README.md#include-hive-skills).
|
|
14
|
+
|
|
7
15
|
## Install from npm
|
|
8
16
|
|
|
9
17
|
Requires Linux, Node.js 24.22.1+ or 26.13.0+, TLS certificates, and `pamtester` for
|
|
@@ -11,12 +19,14 @@ OS-backed sign-in. SQLite is provided by Node; there are no native npm add-ons.
|
|
|
11
19
|
Agent deployment also needs Bash, OpenSSH, and permission to enable the target's user timer.
|
|
12
20
|
|
|
13
21
|
```bash
|
|
14
|
-
npm install --prefix /opt/hive-skills/runtime @hivemagnus/hive-skills@1.0
|
|
22
|
+
npm install --prefix /opt/hive-skills/runtime @hivemagnus/hive-skills@1.1.0
|
|
15
23
|
/opt/hive-skills/runtime/node_modules/.bin/hive-skills --env-file /opt/hive-skills/skills.env
|
|
16
24
|
```
|
|
17
25
|
|
|
18
26
|
Or run `npx @hivemagnus/hive-skills --env-file skills.env`. The CLI accepts `start`
|
|
19
|
-
(the default), `seed`, `--help` and `--version`.
|
|
27
|
+
(the default), `seed`, `check`, `env-schema`, `--help` and `--version`. `check` validates TLS,
|
|
28
|
+
configuration and an existing database without creating or migrating state. `env-schema` prints
|
|
29
|
+
configuration metadata without loading runtime configuration. Environment-file paths may be absolute;
|
|
20
30
|
existing environment variables win. Without `--env-file`, the historical `./.env` lookup
|
|
21
31
|
is retained. Help/version do not initialize storage. A fresh install starts with an empty
|
|
22
32
|
catalog; startup never seeds or replaces existing records.
|
package/dist/check.js
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { accessSync, constants, existsSync, readFileSync, statSync } from 'node:fs';
|
|
2
|
+
import { X509Certificate, createPrivateKey } from 'node:crypto';
|
|
3
|
+
import { isIP } from 'node:net';
|
|
4
|
+
import { dirname, resolve } from 'node:path';
|
|
5
|
+
import { DatabaseSync } from 'node:sqlite';
|
|
6
|
+
/** Read-only preflight: never imports config/app/db, seeds, or creates state. */
|
|
7
|
+
export function checkConfiguration(env) {
|
|
8
|
+
let failures = 0;
|
|
9
|
+
const check = (ok, message) => { console.log(` ${ok ? 'ok ' : 'FAIL'} ${message}`); if (!ok)
|
|
10
|
+
failures++; };
|
|
11
|
+
const port = Number(env.HIVE_SKILLS_PORT ?? '4030');
|
|
12
|
+
check(Number.isInteger(port) && port > 0 && port <= 65535, 'HIVE_SKILLS_PORT is valid');
|
|
13
|
+
check((env.HIVE_SKILLS_SESSION_SECRET?.trim().length ?? 0) >= 32, 'HIVE_SKILLS_SESSION_SECRET has at least 32 characters');
|
|
14
|
+
let hostname;
|
|
15
|
+
try {
|
|
16
|
+
const url = new URL(env.HIVE_SKILLS_PUBLIC_URL ?? `https://localhost:${port}`);
|
|
17
|
+
hostname = url.hostname.replace(/^\[|\]$/g, '');
|
|
18
|
+
check(url.protocol === 'https:' && !url.username && !url.password && !url.search && !url.hash, 'HIVE_SKILLS_PUBLIC_URL is an HTTPS URL without credentials');
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
check(false, 'HIVE_SKILLS_PUBLIC_URL must be a valid HTTPS URL');
|
|
22
|
+
}
|
|
23
|
+
try {
|
|
24
|
+
const cert = new X509Certificate(readFileSync(env.HIVE_SKILLS_TLS_CERT_FILE ?? ''));
|
|
25
|
+
const key = createPrivateKey(readFileSync(env.HIVE_SKILLS_TLS_KEY_FILE ?? ''));
|
|
26
|
+
check(cert.checkPrivateKey(key), 'TLS certificate and key match');
|
|
27
|
+
check(Date.parse(cert.validTo) > Date.now() && Date.parse(cert.validFrom) <= Date.now(), 'TLS certificate is currently valid');
|
|
28
|
+
if (hostname)
|
|
29
|
+
check(Boolean(isIP(hostname) ? cert.checkIP(hostname) : cert.checkHost(hostname)), 'TLS certificate covers the public URL hostname');
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
check(false, 'HIVE_SKILLS_TLS_CERT_FILE and HIVE_SKILLS_TLS_KEY_FILE must be readable valid PEM files');
|
|
33
|
+
}
|
|
34
|
+
const data = resolve(env.HIVE_SKILLS_DATA_DIR ?? './data');
|
|
35
|
+
try {
|
|
36
|
+
let ancestor = data;
|
|
37
|
+
while (!existsSync(ancestor) && dirname(ancestor) !== ancestor)
|
|
38
|
+
ancestor = dirname(ancestor);
|
|
39
|
+
accessSync(ancestor, constants.W_OK | constants.X_OK);
|
|
40
|
+
check(statSync(ancestor).isDirectory(), 'Skills data directory exists and is writable, or can be created');
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
check(false, 'Skills data directory must be writable');
|
|
44
|
+
}
|
|
45
|
+
const database = resolve(data, 'registry.sqlite');
|
|
46
|
+
if (existsSync(database)) {
|
|
47
|
+
let db;
|
|
48
|
+
try {
|
|
49
|
+
db = new DatabaseSync(database, { readOnly: true });
|
|
50
|
+
check(db.prepare('PRAGMA quick_check').get()?.quick_check === 'ok', 'SQLite integrity');
|
|
51
|
+
check(db.prepare('PRAGMA foreign_key_check').all().length === 0, 'SQLite foreign keys');
|
|
52
|
+
db.prepare('SELECT slug FROM skills LIMIT 1').get();
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
check(false, 'Existing database must be a readable Skills registry');
|
|
56
|
+
}
|
|
57
|
+
finally {
|
|
58
|
+
db?.close();
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
else
|
|
62
|
+
console.log(' info Registry database will be created on first start; catalog starts empty');
|
|
63
|
+
if (!env.HIVE_SKILLS_ADMIN_USERS?.trim())
|
|
64
|
+
console.log(' ! HIVE_SKILLS_ADMIN_USERS is empty; OS administration is disabled');
|
|
65
|
+
console.log(failures ? `Result: ${failures} problem(s)` : 'Result: ready to start');
|
|
66
|
+
return failures ? 1 : 0;
|
|
67
|
+
}
|
package/dist/cli.js
CHANGED
|
@@ -4,10 +4,12 @@ import { resolve } from 'node:path';
|
|
|
4
4
|
import { packageRoot } from './paths.js';
|
|
5
5
|
const usage = `Hive Skills: HTTPS skill registry and Hermes distribution bridge.
|
|
6
6
|
|
|
7
|
-
Usage: hive-skills [start|seed] [--env-file FILE]
|
|
7
|
+
Usage: hive-skills [start|seed|check|env-schema] [--env-file FILE]
|
|
8
8
|
hive-skills --help | --version
|
|
9
9
|
|
|
10
10
|
start Start the registry (default); requires TLS key and certificate.
|
|
11
|
+
check Validate configuration, TLS and existing storage without writing.
|
|
12
|
+
env-schema Print configuration metadata as JSON without loading configuration.
|
|
11
13
|
seed Explicitly seed the bundled HiveMem skill; never runs on start.
|
|
12
14
|
--env-file Load a dotenv file; existing environment values win.
|
|
13
15
|
|
|
@@ -25,6 +27,11 @@ export async function main(args) {
|
|
|
25
27
|
console.log(manifest.version);
|
|
26
28
|
return 0;
|
|
27
29
|
}
|
|
30
|
+
if (args.length === 1 && args[0] === 'env-schema') {
|
|
31
|
+
const { envSchemaDocument } = await import('./env-schema.js');
|
|
32
|
+
console.log(JSON.stringify(envSchemaDocument()));
|
|
33
|
+
return 0;
|
|
34
|
+
}
|
|
28
35
|
let command;
|
|
29
36
|
const files = [];
|
|
30
37
|
for (let index = 0; index < args.length; index++) {
|
|
@@ -35,13 +42,23 @@ export async function main(args) {
|
|
|
35
42
|
throw new Error('--env-file requires a file path');
|
|
36
43
|
files.push(file);
|
|
37
44
|
}
|
|
38
|
-
else if (!command && (arg === 'start' || arg === 'seed'))
|
|
45
|
+
else if (!command && (arg === 'start' || arg === 'seed' || arg === 'check'))
|
|
39
46
|
command = arg;
|
|
40
47
|
else
|
|
41
48
|
throw new Error('Invalid arguments. Run hive-skills --help.');
|
|
42
49
|
}
|
|
43
50
|
for (const file of files)
|
|
44
51
|
process.loadEnvFile(resolve(file));
|
|
52
|
+
if (command === 'check') {
|
|
53
|
+
const { checkConfiguration } = await import('./check.js');
|
|
54
|
+
// Preserve the historical cwd env lookup without importing the writable config module.
|
|
55
|
+
if (files.length === 0) {
|
|
56
|
+
const { existsSync } = await import('node:fs');
|
|
57
|
+
if (existsSync('.env'))
|
|
58
|
+
process.loadEnvFile('.env');
|
|
59
|
+
}
|
|
60
|
+
return checkConfiguration(process.env);
|
|
61
|
+
}
|
|
45
62
|
if (command === 'seed')
|
|
46
63
|
await import('./seed.js');
|
|
47
64
|
else
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
3
|
+
import { packageRoot } from './paths.js';
|
|
4
|
+
/** Safe to call before configuration is loaded; contains no runtime values. */
|
|
5
|
+
export function envSchemaDocument() {
|
|
6
|
+
const { version } = JSON.parse(readFileSync(resolve(packageRoot, 'package.json'), 'utf8'));
|
|
7
|
+
return {
|
|
8
|
+
service: 'skills', package: '@hivemagnus/hive-skills', version, defaultPort: 4030,
|
|
9
|
+
healthPath: '/healthz', consolePath: '/', dependencies: [],
|
|
10
|
+
env: [
|
|
11
|
+
{ name: 'NODE_ENV', default: 'production', description: 'Runtime environment.' },
|
|
12
|
+
{ name: 'HIVE_SKILLS_HOST', default: '127.0.0.1', description: 'Listener bind address.' },
|
|
13
|
+
{ name: 'HIVE_SKILLS_PORT', default: '4030', description: 'HTTPS listener port.' },
|
|
14
|
+
{ name: 'HIVE_SKILLS_PUBLIC_URL', default: 'https://localhost:4030', description: 'Public HTTPS registry URL used in archive links.' },
|
|
15
|
+
{ name: 'HIVE_SKILLS_DATA_DIR', default: './data', description: 'SQLite database and immutable skill archives.' },
|
|
16
|
+
{ name: 'HIVE_SKILLS_SESSION_SECRET', required: true, secret: true, generate: 'hex32', description: 'Session signing secret; at least 32 characters.' },
|
|
17
|
+
{ name: 'HIVE_SKILLS_ADMIN_USERS', description: 'Comma-separated OS accounts allowed to administer the registry; independent of IAM.' },
|
|
18
|
+
{ name: 'HIVE_SKILLS_PAM_SERVICE', default: 'hive-skills', description: 'Installed PAM policy for OS authentication; use login for a standard Linux policy.' },
|
|
19
|
+
{ name: 'HIVE_SKILLS_TLS_KEY_FILE', required: true, description: 'TLS private-key PEM file.' },
|
|
20
|
+
{ name: 'HIVE_SKILLS_TLS_CERT_FILE', required: true, description: 'TLS certificate PEM file.' },
|
|
21
|
+
{ name: 'HIVE_SKILLS_DEPLOY_SCRIPT', description: 'Optional override for the bundled agent deployment helper.' },
|
|
22
|
+
{ name: 'HIVE_SKILLS_TARGETS', description: 'Optional initial deployment targets: name=user@host, comma-separated.' },
|
|
23
|
+
].map((entry) => ({ required: false, secret: false, ...entry })),
|
|
24
|
+
};
|
|
25
|
+
}
|