@ours.network/cli 0.6.0 → 1.0.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/README.md +13 -13
- package/dist/boot-35BOHNSZ.js +9 -0
- package/dist/{chunk-6W3RHW3C.js → chunk-6K2JBKHI.js} +5 -6
- package/dist/{chunk-G2GHHOYX.js → chunk-DRQEV4SN.js} +2 -2
- package/dist/chunk-FXKFSNKS.js +166 -0
- package/dist/chunk-MHVYJY3H.js +2530 -0
- package/dist/{chunk-4IFFMMQO.js → chunk-QZIL3IT7.js} +3 -3
- package/dist/{chunk-RJWUCR3R.js → chunk-ZFE47KH3.js} +0 -1
- package/dist/config-command.js +2 -2
- package/dist/connection.js +1 -1
- package/dist/daemon-UUG6P6GK.js +250 -0
- package/dist/help.js +1 -1
- package/dist/lifecycle.js +2 -2
- package/dist/main.js +6 -8
- package/dist/mufl_code/7962BB01B1D3E782A1F80B083A900AEC30BAC8F8CE79282B31821802B94A39B6.muflo +0 -0
- package/dist/server-TDJBEWTU.js +2077 -0
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
# @ours.network/cli
|
|
2
2
|
|
|
3
|
-
`ours` is the transport-neutral operator CLI for the shared
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
`@ours.network/mcp
|
|
3
|
+
`ours` is the transport-neutral operator CLI and lifecycle owner for the shared
|
|
4
|
+
ours daemon. Its artifact bundles the SDK's package-private daemon runtime;
|
|
5
|
+
network operations use the public client attach path. It has no dependency on
|
|
6
|
+
`@ours.network/mcp`, and applications cannot import the runtime through an SDK
|
|
7
|
+
daemon subpath.
|
|
7
8
|
|
|
8
9
|
## Install and first checks
|
|
9
10
|
|
|
@@ -91,12 +92,12 @@ and secret fields already in the file.
|
|
|
91
92
|
```bash
|
|
92
93
|
ours config setup --port 3070 --state-dir /srv/ours-a \
|
|
93
94
|
--broker-url wss://broker1.ours.network --api-visibility owner
|
|
94
|
-
ours config setup --
|
|
95
|
+
ours config setup --gc-interval-ms 3600000
|
|
95
96
|
ours config setup --port 3070 --dry-run --json
|
|
96
97
|
```
|
|
97
98
|
|
|
98
|
-
Supported setup fields are `brokerUrl`, `port`, `stateDir`, `gcIntervalMs`,
|
|
99
|
-
`
|
|
99
|
+
Supported setup fields are `brokerUrl`, `port`, `stateDir`, `gcIntervalMs`, and
|
|
100
|
+
`apiVisibility`. Supply credentials through an operator-owned
|
|
100
101
|
secret mechanism or the existing SDK configuration, not CLI arguments.
|
|
101
102
|
|
|
102
103
|
## Operator commands
|
|
@@ -151,7 +152,7 @@ file fallback probes, and legacy monitoring/control-plane operations.
|
|
|
151
152
|
|
|
152
153
|
## Service management
|
|
153
154
|
|
|
154
|
-
Linux user systemd
|
|
155
|
+
Linux user systemd and macOS launchd are supported through tested adapters:
|
|
155
156
|
|
|
156
157
|
```bash
|
|
157
158
|
ours daemon install-service --dry-run --json
|
|
@@ -159,11 +160,10 @@ ours daemon install-service --yes
|
|
|
159
160
|
ours daemon uninstall-service --yes
|
|
160
161
|
```
|
|
161
162
|
|
|
162
|
-
The
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
success on an unsupported platform.
|
|
163
|
+
The adapters own only units/plists carrying their management marker and refuse
|
|
164
|
+
to overwrite or remove unrelated definitions. The daemon's state-root lock is
|
|
165
|
+
supported on Linux and macOS only; other platforms refuse startup explicitly
|
|
166
|
+
rather than silently running without singleton protection.
|
|
167
167
|
|
|
168
168
|
## License
|
|
169
169
|
|
|
@@ -4,8 +4,7 @@ import { homedir } from "node:os";
|
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import { randomBytes } from "node:crypto";
|
|
6
6
|
import {
|
|
7
|
-
|
|
8
|
-
assertDaemonStateDir,
|
|
7
|
+
attachOursClient,
|
|
9
8
|
describeDaemonConfig,
|
|
10
9
|
resolveDaemonConfig
|
|
11
10
|
} from "@ours.network/sdk/client";
|
|
@@ -26,11 +25,11 @@ function redactedSelection(config) {
|
|
|
26
25
|
return describeDaemonConfig(config);
|
|
27
26
|
}
|
|
28
27
|
async function attachClient(config, opts = {}) {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
28
|
+
return attachOursClient({
|
|
29
|
+
endpoint: config.baseUrl.value,
|
|
30
|
+
expectStateDir: config.expectStateDir,
|
|
31
|
+
token: config.token?.value,
|
|
32
32
|
leaseToken: opts.leaseToken ?? randomBytes(32).toString("hex"),
|
|
33
|
-
apiToken: config.token?.value,
|
|
34
33
|
fetch: opts.fetch
|
|
35
34
|
});
|
|
36
35
|
}
|
|
@@ -2,12 +2,12 @@ import {
|
|
|
2
2
|
defaultConfigPath,
|
|
3
3
|
redactedSelection,
|
|
4
4
|
resolveSelection
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-6K2JBKHI.js";
|
|
6
6
|
|
|
7
7
|
// src/config-command.ts
|
|
8
8
|
import { chmodSync, existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
9
9
|
import { dirname, resolve } from "node:path";
|
|
10
|
-
var SAFE_KEYS = /* @__PURE__ */ new Set(["brokerUrl", "port", "stateDir", "gcIntervalMs", "
|
|
10
|
+
var SAFE_KEYS = /* @__PURE__ */ new Set(["brokerUrl", "port", "stateDir", "gcIntervalMs", "apiVisibility"]);
|
|
11
11
|
async function showConfig(selection) {
|
|
12
12
|
const resolved = await resolveSelection(selection);
|
|
13
13
|
let stored = {};
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
// ../../src/config.ts
|
|
2
|
+
import * as fs from "node:fs";
|
|
3
|
+
import { randomBytes } from "node:crypto";
|
|
4
|
+
import { homedir } from "node:os";
|
|
5
|
+
import { resolve, join, dirname, basename } from "node:path";
|
|
6
|
+
var API_VISIBILITIES = ["owner", "shared", "open"];
|
|
7
|
+
var DEFAULT_CONFIG = {
|
|
8
|
+
brokerUrl: "wss://broker1.ours.network",
|
|
9
|
+
port: 3050,
|
|
10
|
+
stateDir: resolve(homedir(), ".ours"),
|
|
11
|
+
gcIntervalMs: 36e5,
|
|
12
|
+
apiVisibility: "owner"
|
|
13
|
+
};
|
|
14
|
+
function configPath() {
|
|
15
|
+
return process.env.OURS_CONFIG ?? join(homedir(), ".ours", "config.json");
|
|
16
|
+
}
|
|
17
|
+
function readFileConfig() {
|
|
18
|
+
let raw;
|
|
19
|
+
try {
|
|
20
|
+
raw = fs.readFileSync(configPath(), "utf8");
|
|
21
|
+
} catch {
|
|
22
|
+
return {};
|
|
23
|
+
}
|
|
24
|
+
let parsed;
|
|
25
|
+
try {
|
|
26
|
+
parsed = JSON.parse(raw);
|
|
27
|
+
} catch {
|
|
28
|
+
return {};
|
|
29
|
+
}
|
|
30
|
+
const out = {};
|
|
31
|
+
if (typeof parsed.brokerUrl === "string") out.brokerUrl = parsed.brokerUrl;
|
|
32
|
+
if (typeof parsed.port === "number" && Number.isFinite(parsed.port)) out.port = parsed.port;
|
|
33
|
+
if (typeof parsed.stateDir === "string") out.stateDir = resolve(parsed.stateDir);
|
|
34
|
+
if (typeof parsed.gcIntervalMs === "number" && Number.isFinite(parsed.gcIntervalMs)) {
|
|
35
|
+
out.gcIntervalMs = parsed.gcIntervalMs;
|
|
36
|
+
}
|
|
37
|
+
if (typeof parsed.apiVisibility === "string" && API_VISIBILITIES.includes(parsed.apiVisibility)) {
|
|
38
|
+
out.apiVisibility = parsed.apiVisibility;
|
|
39
|
+
}
|
|
40
|
+
if (typeof parsed.apiToken === "string" && parsed.apiToken.trim()) out.apiToken = parsed.apiToken.trim();
|
|
41
|
+
if (parsed.stt && typeof parsed.stt === "object") {
|
|
42
|
+
const s = parsed.stt;
|
|
43
|
+
const stt = {};
|
|
44
|
+
if (typeof s.provider === "string") stt.provider = s.provider;
|
|
45
|
+
if (typeof s.apiKey === "string") stt.apiKey = s.apiKey;
|
|
46
|
+
if (typeof s.model === "string") stt.model = s.model;
|
|
47
|
+
if (typeof s.baseUrl === "string") stt.baseUrl = s.baseUrl;
|
|
48
|
+
if (typeof s.language === "string") stt.language = s.language;
|
|
49
|
+
if (typeof s.maxBytes === "number" && Number.isFinite(s.maxBytes)) stt.maxBytes = s.maxBytes;
|
|
50
|
+
if (typeof s.timeoutMs === "number" && Number.isFinite(s.timeoutMs)) stt.timeoutMs = s.timeoutMs;
|
|
51
|
+
if (s.custom && typeof s.custom === "object") stt.custom = s.custom;
|
|
52
|
+
out.stt = stt;
|
|
53
|
+
}
|
|
54
|
+
return out;
|
|
55
|
+
}
|
|
56
|
+
function envVisibility() {
|
|
57
|
+
const v = process.env.OURS_API_VISIBILITY?.trim().toLowerCase();
|
|
58
|
+
return v && API_VISIBILITIES.includes(v) ? v : void 0;
|
|
59
|
+
}
|
|
60
|
+
function envInt(name) {
|
|
61
|
+
const v = process.env[name];
|
|
62
|
+
if (v === void 0) return void 0;
|
|
63
|
+
const n = parseInt(v, 10);
|
|
64
|
+
return Number.isNaN(n) ? void 0 : n;
|
|
65
|
+
}
|
|
66
|
+
function loadConfig() {
|
|
67
|
+
const file = readFileConfig();
|
|
68
|
+
return {
|
|
69
|
+
brokerUrl: process.env.OURS_BROKER_URL ?? file.brokerUrl ?? DEFAULT_CONFIG.brokerUrl,
|
|
70
|
+
port: envInt("OURS_PORT") ?? file.port ?? DEFAULT_CONFIG.port,
|
|
71
|
+
stateDir: resolve(process.env.OURS_STATE_DIR ?? file.stateDir ?? DEFAULT_CONFIG.stateDir),
|
|
72
|
+
gcIntervalMs: envInt("OURS_GC_INTERVAL_MS") ?? file.gcIntervalMs ?? DEFAULT_CONFIG.gcIntervalMs,
|
|
73
|
+
apiVisibility: envVisibility() ?? file.apiVisibility ?? DEFAULT_CONFIG.apiVisibility,
|
|
74
|
+
apiToken: process.env.OURS_API_TOKEN?.trim() || file.apiToken,
|
|
75
|
+
stt: sttFromEnv(file.stt)
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
function sttFromEnv(file) {
|
|
79
|
+
const env = {};
|
|
80
|
+
if (process.env.OURS_STT_PROVIDER?.trim()) env.provider = process.env.OURS_STT_PROVIDER.trim();
|
|
81
|
+
if (process.env.OURS_STT_API_KEY?.trim()) env.apiKey = process.env.OURS_STT_API_KEY.trim();
|
|
82
|
+
if (process.env.OURS_STT_MODEL?.trim()) env.model = process.env.OURS_STT_MODEL.trim();
|
|
83
|
+
if (process.env.OURS_STT_BASE_URL?.trim()) env.baseUrl = process.env.OURS_STT_BASE_URL.trim();
|
|
84
|
+
if (process.env.OURS_STT_LANGUAGE?.trim()) env.language = process.env.OURS_STT_LANGUAGE.trim();
|
|
85
|
+
const merged = { ...file ?? {}, ...env };
|
|
86
|
+
return Object.keys(merged).length ? merged : void 0;
|
|
87
|
+
}
|
|
88
|
+
var API_TOKEN_FILENAME = "daemon-token";
|
|
89
|
+
function apiTokenPath(cfg) {
|
|
90
|
+
return join(cfg.stateDir, API_TOKEN_FILENAME);
|
|
91
|
+
}
|
|
92
|
+
function explicitApiToken(cfg) {
|
|
93
|
+
const env = process.env.OURS_API_TOKEN?.trim();
|
|
94
|
+
if (env) return env;
|
|
95
|
+
const c = cfg.apiToken?.trim();
|
|
96
|
+
return c || void 0;
|
|
97
|
+
}
|
|
98
|
+
function resolveApiToken(cfg, opts = {}) {
|
|
99
|
+
const env = process.env.OURS_API_TOKEN?.trim();
|
|
100
|
+
if (env) return { token: env, source: "env" };
|
|
101
|
+
const c = cfg.apiToken?.trim();
|
|
102
|
+
if (c) return { token: c, source: "config" };
|
|
103
|
+
const path = apiTokenPath(cfg);
|
|
104
|
+
try {
|
|
105
|
+
const fromFile = fs.readFileSync(path, "utf8").trim();
|
|
106
|
+
if (fromFile) return { token: fromFile, source: "file" };
|
|
107
|
+
} catch {
|
|
108
|
+
}
|
|
109
|
+
if (!opts.generate) return null;
|
|
110
|
+
const token = randomBytes(32).toString("hex");
|
|
111
|
+
try {
|
|
112
|
+
fs.mkdirSync(dirname(path), { recursive: true });
|
|
113
|
+
fs.writeFileSync(path, token + "\n", { mode: 384 });
|
|
114
|
+
fs.chmodSync(path, 384);
|
|
115
|
+
} catch {
|
|
116
|
+
}
|
|
117
|
+
return { token, source: "generated" };
|
|
118
|
+
}
|
|
119
|
+
var IDENTITY_FILENAME = ".ours-identity";
|
|
120
|
+
function buildIdentityFile(opts) {
|
|
121
|
+
if (!opts.name.trim()) throw new Error("identity name must not be empty");
|
|
122
|
+
const obj = { identity: opts.name.trim() };
|
|
123
|
+
if (opts.force) obj.force = true;
|
|
124
|
+
obj.expose_local = opts.exposeLocal ?? true;
|
|
125
|
+
obj.local_auto_accept = opts.localAutoAccept ?? true;
|
|
126
|
+
return obj;
|
|
127
|
+
}
|
|
128
|
+
function resolveIdentityFilePath(target) {
|
|
129
|
+
const abs = resolve(target);
|
|
130
|
+
return basename(abs) === IDENTITY_FILENAME ? abs : join(abs, IDENTITY_FILENAME);
|
|
131
|
+
}
|
|
132
|
+
function writeIdentityFile(target, opts, overwrite = false) {
|
|
133
|
+
const obj = buildIdentityFile(opts);
|
|
134
|
+
const path = resolveIdentityFilePath(target);
|
|
135
|
+
if (!overwrite && fs.existsSync(path)) {
|
|
136
|
+
throw new Error(`${path} already exists \u2014 pass overwrite to replace it`);
|
|
137
|
+
}
|
|
138
|
+
fs.mkdirSync(dirname(path), { recursive: true });
|
|
139
|
+
fs.writeFileSync(path, JSON.stringify(obj, null, 2) + "\n");
|
|
140
|
+
return path;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// ../../src/runtime/config-drift.ts
|
|
144
|
+
var FROZEN_ENV = ["OURS_STATE_DIR", "OURS_BROKER_URL", "OURS_PORT", "OURS_API_VISIBILITY", "OURS_CONFIG"];
|
|
145
|
+
var envAtLoad = Object.fromEntries(
|
|
146
|
+
FROZEN_ENV.map((k) => [k, process.env[k]])
|
|
147
|
+
);
|
|
148
|
+
var { stateDir: loadedStateDir, brokerUrl: loadedBrokerUrl } = loadConfig();
|
|
149
|
+
function assertConfigNotDrifted() {
|
|
150
|
+
const drifted = FROZEN_ENV.filter((k) => process.env[k] !== envAtLoad[k]);
|
|
151
|
+
if (drifted.length === 0) return;
|
|
152
|
+
const detail = drifted.map((k) => `${k}: was ${JSON.stringify(envAtLoad[k] ?? "(unset)")} at import, is now ${JSON.stringify(process.env[k] ?? "(unset)")}`).join("; ");
|
|
153
|
+
throw new Error(
|
|
154
|
+
`configuration changed after @ours.network/sdk was imported (${detail}). The SDK reads its config ONCE, at module load, because CONFIG and STATE_DIR back process-wide singletons that cannot be re-pointed once identities exist. Setting these after the import does not fail and does not take effect \u2014 without this check the daemon would boot against ${JSON.stringify(loadedStateDir)} and broker ${JSON.stringify(loadedBrokerUrl)} and look like it worked. Set every OURS_* config variable BEFORE the first import of this package.`
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export {
|
|
159
|
+
loadConfig,
|
|
160
|
+
apiTokenPath,
|
|
161
|
+
explicitApiToken,
|
|
162
|
+
resolveApiToken,
|
|
163
|
+
buildIdentityFile,
|
|
164
|
+
writeIdentityFile,
|
|
165
|
+
assertConfigNotDrifted
|
|
166
|
+
};
|