@omnicross/daemon 0.1.3 → 0.1.5
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/dist/cli.cjs +80 -50
- package/dist/cli.js +66 -36
- package/dist/index.cjs +85 -53
- package/dist/index.d.cts +7 -2
- package/dist/index.d.ts +7 -2
- package/dist/index.js +72 -40
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -38,6 +38,7 @@ var CodexOAuthSessionStore = class {
|
|
|
38
38
|
ttlMs;
|
|
39
39
|
sessions = /* @__PURE__ */ new Map();
|
|
40
40
|
activeSessionId = null;
|
|
41
|
+
aborters = /* @__PURE__ */ new Map();
|
|
41
42
|
/** Whether a codex sign-in is currently in flight (port 1455 held). */
|
|
42
43
|
isBusy() {
|
|
43
44
|
this.sweep();
|
|
@@ -49,13 +50,22 @@ var CodexOAuthSessionStore = class {
|
|
|
49
50
|
const sessionId = crypto.randomBytes(24).toString("base64url");
|
|
50
51
|
this.sessions.set(sessionId, { status: "pending", createdAt: Date.now() });
|
|
51
52
|
this.activeSessionId = sessionId;
|
|
52
|
-
|
|
53
|
+
const controller = new AbortController();
|
|
54
|
+
this.aborters.set(sessionId, controller);
|
|
55
|
+
return { sessionId, signal: controller.signal };
|
|
53
56
|
}
|
|
54
57
|
/** Settle a flow (done/error) + free the active slot. */
|
|
55
58
|
settle(sessionId, status, error) {
|
|
56
59
|
const prior = this.sessions.get(sessionId);
|
|
57
60
|
this.sessions.set(sessionId, { status, error, createdAt: prior?.createdAt ?? Date.now() });
|
|
58
61
|
if (this.activeSessionId === sessionId) this.activeSessionId = null;
|
|
62
|
+
this.aborters.delete(sessionId);
|
|
63
|
+
}
|
|
64
|
+
cancel(sessionId) {
|
|
65
|
+
if (!this.sessions.has(sessionId)) return false;
|
|
66
|
+
this.aborters.get(sessionId)?.abort();
|
|
67
|
+
this.settle(sessionId, "error", "login: cancelled");
|
|
68
|
+
return true;
|
|
59
69
|
}
|
|
60
70
|
/** Read a flow's status (token-free), or null when unknown/expired. */
|
|
61
71
|
get(sessionId) {
|
|
@@ -84,13 +94,13 @@ function handleCodexOAuthStart(deps) {
|
|
|
84
94
|
);
|
|
85
95
|
}
|
|
86
96
|
const { authUrl, codeVerifier, state } = codexOAuth.generateAuthParams();
|
|
87
|
-
const sessionId = deps.codexSessions.begin();
|
|
88
|
-
void runCodexLoopback(sessionId, codeVerifier, state, deps);
|
|
97
|
+
const { sessionId, signal } = deps.codexSessions.begin();
|
|
98
|
+
void runCodexLoopback(sessionId, codeVerifier, state, signal, deps);
|
|
89
99
|
return { status: 200, body: { authUrl, sessionId } };
|
|
90
100
|
}
|
|
91
|
-
async function runCodexLoopback(sessionId, codeVerifier, state, deps) {
|
|
101
|
+
async function runCodexLoopback(sessionId, codeVerifier, state, signal, deps) {
|
|
92
102
|
try {
|
|
93
|
-
const code = await deps.codexAwaitLoopback(state);
|
|
103
|
+
const code = await deps.codexAwaitLoopback(state, void 0, signal);
|
|
94
104
|
const result = await codexOAuth.exchangeCodeForTokens(
|
|
95
105
|
{ authorizationCode: code, codeVerifier, state },
|
|
96
106
|
deps.oauthExchangeFetch
|
|
@@ -112,6 +122,10 @@ async function runCodexLoopback(sessionId, codeVerifier, state, deps) {
|
|
|
112
122
|
deps.codexSessions.settle(sessionId, "error", reason);
|
|
113
123
|
}
|
|
114
124
|
}
|
|
125
|
+
function handleCodexOAuthCancel(sessionId, deps) {
|
|
126
|
+
if (!deps.codexSessions.cancel(sessionId)) return err(404, "unknown or expired codex sign-in session");
|
|
127
|
+
return { status: 200, body: { ok: true } };
|
|
128
|
+
}
|
|
115
129
|
function handleCodexOAuthStatus(sessionId, deps) {
|
|
116
130
|
const s = deps.codexSessions.get(sessionId);
|
|
117
131
|
if (!s) return err(404, "unknown or expired codex sign-in session");
|
|
@@ -1874,12 +1888,16 @@ function preserveWebhookSecrets(incoming, current) {
|
|
|
1874
1888
|
}
|
|
1875
1889
|
|
|
1876
1890
|
// src/audit/auditRuntime.ts
|
|
1891
|
+
import { join as join3 } from "path";
|
|
1877
1892
|
import { setAuditCaptureConfig, setAuditSink } from "@omnicross/core/pipeline/auditSink";
|
|
1893
|
+
import { setUpstreamTracePath } from "@omnicross/core/pipeline/upstreamTrace";
|
|
1878
1894
|
var writer = null;
|
|
1879
1895
|
var sweeper = null;
|
|
1880
|
-
|
|
1896
|
+
var auditDir = "";
|
|
1897
|
+
function setAuditRuntime(w, s, dir) {
|
|
1881
1898
|
writer = w;
|
|
1882
1899
|
sweeper = s;
|
|
1900
|
+
auditDir = dir;
|
|
1883
1901
|
}
|
|
1884
1902
|
function applyAuditConfig(config) {
|
|
1885
1903
|
const enabled = config?.enabled === true && writer !== null;
|
|
@@ -1891,9 +1909,11 @@ function applyAuditConfig(config) {
|
|
|
1891
1909
|
sweeper.configure(config);
|
|
1892
1910
|
sweeper.start();
|
|
1893
1911
|
}
|
|
1912
|
+
setUpstreamTracePath(config.captureBodies ? join3(auditDir, "upstream-trace.jsonl") : null);
|
|
1894
1913
|
} else {
|
|
1895
1914
|
setAuditCaptureConfig(null);
|
|
1896
1915
|
setAuditSink(null);
|
|
1916
|
+
setUpstreamTracePath(null);
|
|
1897
1917
|
if (sweeper) {
|
|
1898
1918
|
if (config) sweeper.configure(config);
|
|
1899
1919
|
sweeper.dispose();
|
|
@@ -1903,9 +1923,11 @@ function applyAuditConfig(config) {
|
|
|
1903
1923
|
function resetAuditRuntimeForTests() {
|
|
1904
1924
|
setAuditCaptureConfig(null);
|
|
1905
1925
|
setAuditSink(null);
|
|
1926
|
+
setUpstreamTracePath(null);
|
|
1906
1927
|
if (sweeper) sweeper.dispose();
|
|
1907
1928
|
writer = null;
|
|
1908
1929
|
sweeper = null;
|
|
1930
|
+
auditDir = "";
|
|
1909
1931
|
}
|
|
1910
1932
|
|
|
1911
1933
|
// src/billing/billingRuntime.ts
|
|
@@ -3520,6 +3542,10 @@ async function handleAccounts(req, res, method, rest, deps) {
|
|
|
3520
3542
|
const result = handleCodexOAuthStatus(rest[2], deps);
|
|
3521
3543
|
return writeJson2(res, result.status, result.body);
|
|
3522
3544
|
}
|
|
3545
|
+
if (method === "DELETE" && rest[0] === "codex" && rest[1] === "oauth" && rest[2]) {
|
|
3546
|
+
const result = handleCodexOAuthCancel(rest[2], deps);
|
|
3547
|
+
return writeJson2(res, result.status, result.body);
|
|
3548
|
+
}
|
|
3523
3549
|
if (method === "PUT" || method === "POST" || method === "DELETE") {
|
|
3524
3550
|
const providerId = asSubscriptionProviderId(rest[0]);
|
|
3525
3551
|
if (!providerId) {
|
|
@@ -3859,7 +3885,7 @@ async function handleUiStatic(req, res, urlPath, uiDist) {
|
|
|
3859
3885
|
}
|
|
3860
3886
|
|
|
3861
3887
|
// src/admin/version.ts
|
|
3862
|
-
var DAEMON_VERSION = true ? "0.1.
|
|
3888
|
+
var DAEMON_VERSION = true ? "0.1.5" : "0.0.0-dev";
|
|
3863
3889
|
|
|
3864
3890
|
// src/admin/AdminServer.ts
|
|
3865
3891
|
var LOOPBACK_ADDR = "127.0.0.1";
|
|
@@ -4114,7 +4140,7 @@ var DEFAULT_TIMEOUT_MS = 5 * 6e4;
|
|
|
4114
4140
|
function pageHtml(message) {
|
|
4115
4141
|
return `<!doctype html><meta charset="utf-8"><title>omnicross login</title><body style="font-family:sans-serif;padding:2rem"><h2>${message}</h2><p>You can close this window and return to the terminal.</p></body>`;
|
|
4116
4142
|
}
|
|
4117
|
-
function awaitLoopbackCode(expectedState, timeoutMs = DEFAULT_TIMEOUT_MS) {
|
|
4143
|
+
function awaitLoopbackCode(expectedState, timeoutMs = DEFAULT_TIMEOUT_MS, signal) {
|
|
4118
4144
|
return new Promise((resolve, reject) => {
|
|
4119
4145
|
let settled = false;
|
|
4120
4146
|
const finish = (server2, fn) => {
|
|
@@ -4148,6 +4174,12 @@ function awaitLoopbackCode(expectedState, timeoutMs = DEFAULT_TIMEOUT_MS) {
|
|
|
4148
4174
|
res.end(pageHtml("Login complete."));
|
|
4149
4175
|
finish(server, () => resolve(code));
|
|
4150
4176
|
});
|
|
4177
|
+
const abort = () => finish(server, () => reject(new Error("login: cancelled")));
|
|
4178
|
+
if (signal?.aborted) {
|
|
4179
|
+
abort();
|
|
4180
|
+
return;
|
|
4181
|
+
}
|
|
4182
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
4151
4183
|
server.on("error", (err5) => {
|
|
4152
4184
|
if (settled) return;
|
|
4153
4185
|
settled = true;
|
|
@@ -4235,21 +4267,21 @@ function createPoolKeysLoader(getProviderRow, autoDisabled) {
|
|
|
4235
4267
|
}
|
|
4236
4268
|
|
|
4237
4269
|
// src/commands/paths.ts
|
|
4238
|
-
import { dirname as dirname2, join as
|
|
4270
|
+
import { dirname as dirname2, join as join4 } from "path";
|
|
4239
4271
|
function defaultVouchersPath(configPath) {
|
|
4240
|
-
return
|
|
4272
|
+
return join4(dirname2(configPath), "vouchers.json");
|
|
4241
4273
|
}
|
|
4242
4274
|
function defaultPricingPath(configPath) {
|
|
4243
|
-
return
|
|
4275
|
+
return join4(dirname2(configPath), "pricing.json");
|
|
4244
4276
|
}
|
|
4245
4277
|
function defaultUsageEventsPath(configPath) {
|
|
4246
|
-
return
|
|
4278
|
+
return join4(dirname2(configPath), "usage-events.jsonl");
|
|
4247
4279
|
}
|
|
4248
4280
|
function defaultAuditDir(configPath) {
|
|
4249
|
-
return
|
|
4281
|
+
return join4(dirname2(configPath), "audit");
|
|
4250
4282
|
}
|
|
4251
4283
|
function defaultBillingDir(configPath) {
|
|
4252
|
-
return
|
|
4284
|
+
return join4(dirname2(configPath), "billing");
|
|
4253
4285
|
}
|
|
4254
4286
|
|
|
4255
4287
|
// src/ports/ConfigFileProviderConfigSource.ts
|
|
@@ -5335,9 +5367,9 @@ function findDuplicateCredentialIds(accounts) {
|
|
|
5335
5367
|
// src/ports/external-cli-credentials.ts
|
|
5336
5368
|
import { existsSync as existsSync8, readFileSync as readFileSync8 } from "fs";
|
|
5337
5369
|
import { homedir as homedir2 } from "os";
|
|
5338
|
-
import { join as
|
|
5370
|
+
import { join as join5 } from "path";
|
|
5339
5371
|
function externalStorePath(provider, home = homedir2()) {
|
|
5340
|
-
return provider === "claude" ?
|
|
5372
|
+
return provider === "claude" ? join5(home, ".claude", ".credentials.json") : join5(home, ".codex", "auth.json");
|
|
5341
5373
|
}
|
|
5342
5374
|
function decodeJwtExpiryMs(token) {
|
|
5343
5375
|
try {
|
|
@@ -6456,7 +6488,7 @@ var AccountHealthSweeper = class {
|
|
|
6456
6488
|
|
|
6457
6489
|
// src/audit/AuditPruneSweeper.ts
|
|
6458
6490
|
import { existsSync as existsSync11, readdirSync, unlinkSync } from "fs";
|
|
6459
|
-
import { join as
|
|
6491
|
+
import { join as join6 } from "path";
|
|
6460
6492
|
|
|
6461
6493
|
// src/audit/auditFiles.ts
|
|
6462
6494
|
var AUDIT_FILE_RE = /^audit-(\d{4})-(\d{2})-(\d{2})\.jsonl$/;
|
|
@@ -6482,8 +6514,8 @@ function auditFileDateMs(fileName) {
|
|
|
6482
6514
|
var DAY_MS = 24 * 60 * 6e4;
|
|
6483
6515
|
var SWEEP_INTERVAL_MS2 = 60 * 6e4;
|
|
6484
6516
|
var AuditPruneSweeper = class {
|
|
6485
|
-
constructor(
|
|
6486
|
-
this.auditDir =
|
|
6517
|
+
constructor(auditDir2, logger, config, intervalMs = SWEEP_INTERVAL_MS2, now = Date.now) {
|
|
6518
|
+
this.auditDir = auditDir2;
|
|
6487
6519
|
this.logger = logger;
|
|
6488
6520
|
this.config = config;
|
|
6489
6521
|
this.intervalMs = intervalMs;
|
|
@@ -6539,7 +6571,7 @@ var AuditPruneSweeper = class {
|
|
|
6539
6571
|
const dateMs = auditFileDateMs(file);
|
|
6540
6572
|
if (dateMs === null || dateMs >= cutoff) continue;
|
|
6541
6573
|
try {
|
|
6542
|
-
unlinkSync(
|
|
6574
|
+
unlinkSync(join6(this.auditDir, file));
|
|
6543
6575
|
removed += 1;
|
|
6544
6576
|
} catch (error) {
|
|
6545
6577
|
this.logger.warn("[AuditPruneSweeper] failed to unlink expired audit file", {
|
|
@@ -6563,14 +6595,14 @@ var AuditPruneSweeper = class {
|
|
|
6563
6595
|
|
|
6564
6596
|
// src/audit/auditReader.ts
|
|
6565
6597
|
import { existsSync as existsSync12, readdirSync as readdirSync2, readFileSync as readFileSync11 } from "fs";
|
|
6566
|
-
import { join as
|
|
6598
|
+
import { join as join7 } from "path";
|
|
6567
6599
|
var DEFAULT_LIMIT = 200;
|
|
6568
6600
|
var MAX_LIMIT = 2e3;
|
|
6569
|
-
function readAuditRecords(
|
|
6570
|
-
if (!existsSync12(
|
|
6601
|
+
function readAuditRecords(auditDir2, query = {}) {
|
|
6602
|
+
if (!existsSync12(auditDir2)) return [];
|
|
6571
6603
|
let files;
|
|
6572
6604
|
try {
|
|
6573
|
-
files = readdirSync2(
|
|
6605
|
+
files = readdirSync2(auditDir2).filter((f) => AUDIT_FILE_RE.test(f));
|
|
6574
6606
|
} catch {
|
|
6575
6607
|
return [];
|
|
6576
6608
|
}
|
|
@@ -6581,7 +6613,7 @@ function readAuditRecords(auditDir, query = {}) {
|
|
|
6581
6613
|
for (const file of files.sort().reverse()) {
|
|
6582
6614
|
let raw;
|
|
6583
6615
|
try {
|
|
6584
|
-
raw = readFileSync11(
|
|
6616
|
+
raw = readFileSync11(join7(auditDir2, file), "utf8");
|
|
6585
6617
|
} catch {
|
|
6586
6618
|
continue;
|
|
6587
6619
|
}
|
|
@@ -6611,10 +6643,10 @@ function isAuditRecord(value) {
|
|
|
6611
6643
|
|
|
6612
6644
|
// src/audit/AuditWriter.ts
|
|
6613
6645
|
import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync4 } from "fs";
|
|
6614
|
-
import { join as
|
|
6646
|
+
import { join as join8 } from "path";
|
|
6615
6647
|
var AuditWriter = class {
|
|
6616
|
-
constructor(
|
|
6617
|
-
this.auditDir =
|
|
6648
|
+
constructor(auditDir2, logger, defer = (fn) => setTimeout(fn, 0)) {
|
|
6649
|
+
this.auditDir = auditDir2;
|
|
6618
6650
|
this.logger = logger;
|
|
6619
6651
|
this.defer = defer;
|
|
6620
6652
|
}
|
|
@@ -6647,7 +6679,7 @@ var AuditWriter = class {
|
|
|
6647
6679
|
mkdirSync4(this.auditDir, { recursive: true });
|
|
6648
6680
|
this.dirEnsured = true;
|
|
6649
6681
|
}
|
|
6650
|
-
const file =
|
|
6682
|
+
const file = join8(this.auditDir, auditFileName(record.ts));
|
|
6651
6683
|
appendFileSync2(file, JSON.stringify(record) + "\n", "utf8");
|
|
6652
6684
|
}
|
|
6653
6685
|
};
|
|
@@ -6655,7 +6687,7 @@ var AuditWriter = class {
|
|
|
6655
6687
|
// src/billing/BillingPublisher.ts
|
|
6656
6688
|
import { appendFileSync as appendFileSync3, mkdirSync as mkdirSync5 } from "fs";
|
|
6657
6689
|
import { createHmac } from "crypto";
|
|
6658
|
-
import { join as
|
|
6690
|
+
import { join as join9 } from "path";
|
|
6659
6691
|
import { fetchUpstream as fetchUpstream4 } from "@omnicross/core/pipeline/upstreamFetch";
|
|
6660
6692
|
|
|
6661
6693
|
// src/billing/billingFiles.ts
|
|
@@ -6726,7 +6758,7 @@ var BillingPublisher = class {
|
|
|
6726
6758
|
*/
|
|
6727
6759
|
appendNow(event) {
|
|
6728
6760
|
this.ensureDir();
|
|
6729
|
-
const file =
|
|
6761
|
+
const file = join9(this.billingDir, billingFileName(event.ts));
|
|
6730
6762
|
appendFileSync3(file, JSON.stringify(event) + "\n", "utf8");
|
|
6731
6763
|
}
|
|
6732
6764
|
/**
|
|
@@ -6776,7 +6808,7 @@ var BillingPublisher = class {
|
|
|
6776
6808
|
markDelivered(event) {
|
|
6777
6809
|
try {
|
|
6778
6810
|
this.ensureDir();
|
|
6779
|
-
const file =
|
|
6811
|
+
const file = join9(this.billingDir, deliveredFileName(event.ts));
|
|
6780
6812
|
appendFileSync3(file, JSON.stringify({ id: event.id, deliveredAt: this.now() }) + "\n", "utf8");
|
|
6781
6813
|
} catch (error) {
|
|
6782
6814
|
this.logger.warn("[BillingPublisher] failed to append delivery marker", {
|
|
@@ -6793,7 +6825,7 @@ var BillingPublisher = class {
|
|
|
6793
6825
|
|
|
6794
6826
|
// src/billing/billingReader.ts
|
|
6795
6827
|
import { existsSync as existsSync13, readdirSync as readdirSync3, readFileSync as readFileSync12 } from "fs";
|
|
6796
|
-
import { join as
|
|
6828
|
+
import { join as join10 } from "path";
|
|
6797
6829
|
function readBillingLedger(billingDir) {
|
|
6798
6830
|
const view = { events: [], deliveredIds: /* @__PURE__ */ new Set() };
|
|
6799
6831
|
if (!existsSync13(billingDir)) return view;
|
|
@@ -6830,7 +6862,7 @@ function readBillingStatus(billingDir) {
|
|
|
6830
6862
|
function parseLines(dir, file) {
|
|
6831
6863
|
let raw;
|
|
6832
6864
|
try {
|
|
6833
|
-
raw = readFileSync12(
|
|
6865
|
+
raw = readFileSync12(join10(dir, file), "utf8");
|
|
6834
6866
|
} catch {
|
|
6835
6867
|
return [];
|
|
6836
6868
|
}
|
|
@@ -7271,7 +7303,7 @@ function buildDaemon(config, paths) {
|
|
|
7271
7303
|
// lines through the injected logger (honors level/format/file sink).
|
|
7272
7304
|
logger
|
|
7273
7305
|
});
|
|
7274
|
-
const
|
|
7306
|
+
const auditDir2 = defaultAuditDir(paths.configPath);
|
|
7275
7307
|
const billingDir = defaultBillingDir(paths.configPath);
|
|
7276
7308
|
const adminServer = new AdminServer({
|
|
7277
7309
|
configPath: paths.configPath,
|
|
@@ -7312,7 +7344,7 @@ function buildDaemon(config, paths) {
|
|
|
7312
7344
|
// the app polls the token-free status. A test seam (`paths.codexAwaitLoopback`)
|
|
7313
7345
|
// can inject a mock so no real port is bound.
|
|
7314
7346
|
codexSessions: new CodexOAuthSessionStore(),
|
|
7315
|
-
codexAwaitLoopback: paths.codexAwaitLoopback ?? ((state, timeoutMs) => awaitLoopbackCode(state, timeoutMs)),
|
|
7347
|
+
codexAwaitLoopback: paths.codexAwaitLoopback ?? ((state, timeoutMs, signal) => awaitLoopbackCode(state, timeoutMs, signal)),
|
|
7316
7348
|
// Migration pack (app-parity child 6, design D2/D3) — the concrete credential
|
|
7317
7349
|
// store provides BOTH the full DECRYPTED read (`getFullConfig`, export) and
|
|
7318
7350
|
// the multi-account append (`appendProviderAccount`, import re-encrypts at-
|
|
@@ -7347,7 +7379,7 @@ function buildDaemon(config, paths) {
|
|
|
7347
7379
|
// date-rotated audit store. Bound to the store dir here so the AdminServer
|
|
7348
7380
|
// carries no path/store coupling. Records hold IP/UA/bodies → admin-only,
|
|
7349
7381
|
// NEVER unauth, NEVER on `/health`. Routed in `AdminServer` (not `adminApi.ts`).
|
|
7350
|
-
auditReader: (query) => readAuditRecords(
|
|
7382
|
+
auditReader: (query) => readAuditRecords(auditDir2, query),
|
|
7351
7383
|
// billing-event-stream: the AUTHED `GET /admin/api/billing-status` returns the
|
|
7352
7384
|
// secret-free total/delivered/pending counts of the durable ledger.
|
|
7353
7385
|
billingStatusReader: () => readBillingStatus(billingDir)
|
|
@@ -7357,9 +7389,9 @@ function buildDaemon(config, paths) {
|
|
|
7357
7389
|
fetchImpl: (url, init) => fetchUpstream6(url, init)
|
|
7358
7390
|
});
|
|
7359
7391
|
setWebhookRuntime(webhookDispatcher, getSharedAccountHealth2());
|
|
7360
|
-
const auditWriter = new AuditWriter(
|
|
7361
|
-
const auditPruneSweeper = new AuditPruneSweeper(
|
|
7362
|
-
setAuditRuntime(auditWriter, auditPruneSweeper);
|
|
7392
|
+
const auditWriter = new AuditWriter(auditDir2, logger);
|
|
7393
|
+
const auditPruneSweeper = new AuditPruneSweeper(auditDir2, logger, DEFAULT_AUDIT_CONFIG);
|
|
7394
|
+
setAuditRuntime(auditWriter, auditPruneSweeper, auditDir2);
|
|
7363
7395
|
const billingPublisher = new BillingPublisher(billingDir, logger);
|
|
7364
7396
|
const billingRetrySweeper = new BillingRetrySweeper(
|
|
7365
7397
|
billingDir,
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@omnicross/daemon",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "Omnicross standalone daemon
|
|
3
|
+
"version": "0.1.5",
|
|
4
|
+
"description": "Omnicross standalone daemon 鈥?the bare-Node embedder of @omnicross/core with an admin HTTP API + dashboard.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Sayo (https://github.com/Dumoedss)",
|
|
7
7
|
"homepage": "https://github.com/Dumoedss/omnicross#readme",
|