@xdsjs/dossierx-daemon 0.1.3 → 0.1.4
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/index.js +447 -47
- package/package.json +5 -4
package/dist/index.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/cli.ts
|
|
4
|
-
import { mkdir as
|
|
4
|
+
import { mkdir as mkdir7, stat as stat4 } from "fs/promises";
|
|
5
5
|
import os3 from "os";
|
|
6
|
-
import
|
|
6
|
+
import path10 from "path";
|
|
7
7
|
import { Command } from "commander";
|
|
8
8
|
import { DOSSIERX_DEFAULT_WORKSPACE_PATH } from "@xdsjs/dossierx-workspace";
|
|
9
9
|
|
|
@@ -30,8 +30,8 @@ var ApiClient = class {
|
|
|
30
30
|
this.machineKey = options.machineKey;
|
|
31
31
|
this.fetchImpl = options.fetchImpl ?? fetch;
|
|
32
32
|
}
|
|
33
|
-
async post(
|
|
34
|
-
const response = await this.fetchImpl(`${this.serverUrl}${
|
|
33
|
+
async post(path11, body, schema) {
|
|
34
|
+
const response = await this.fetchImpl(`${this.serverUrl}${path11}`, {
|
|
35
35
|
method: "POST",
|
|
36
36
|
headers: {
|
|
37
37
|
authorization: `Bearer ${this.machineKey}`,
|
|
@@ -738,6 +738,7 @@ import { readFile as readFile3, realpath, stat } from "fs/promises";
|
|
|
738
738
|
import path5 from "path";
|
|
739
739
|
import { execa as execa3 } from "execa";
|
|
740
740
|
import {
|
|
741
|
+
readCoverageContractBundle,
|
|
741
742
|
resolveInsideWorkspace as resolveInsideWorkspace2,
|
|
742
743
|
scanCompanyManifest
|
|
743
744
|
} from "@xdsjs/dossierx-workspace";
|
|
@@ -819,7 +820,7 @@ function createTaskArchive(options) {
|
|
|
819
820
|
|
|
820
821
|
// src/version.ts
|
|
821
822
|
var DAEMON_PACKAGE_NAME = "@xdsjs/dossierx-daemon";
|
|
822
|
-
var DAEMON_VERSION = "0.1.
|
|
823
|
+
var DAEMON_VERSION = "0.1.4";
|
|
823
824
|
|
|
824
825
|
// src/local-api/server.ts
|
|
825
826
|
var DEFAULT_MAX_FILE_BYTES = 5 * 1024 * 1024;
|
|
@@ -1191,6 +1192,34 @@ async function startWorkspaceReadServer(options) {
|
|
|
1191
1192
|
}
|
|
1192
1193
|
return;
|
|
1193
1194
|
}
|
|
1195
|
+
if (request.method === "GET" && url.pathname === "/coverage/bundle") {
|
|
1196
|
+
const bundlePath = url.searchParams.get("path");
|
|
1197
|
+
if (!bundlePath) {
|
|
1198
|
+
json(response, 400, { error: "Missing coverage bundle path" }, origin);
|
|
1199
|
+
return;
|
|
1200
|
+
}
|
|
1201
|
+
try {
|
|
1202
|
+
json(
|
|
1203
|
+
response,
|
|
1204
|
+
200,
|
|
1205
|
+
await readCoverageContractBundle({
|
|
1206
|
+
workspaceRoot: options.workspaceRoot,
|
|
1207
|
+
bundlePath
|
|
1208
|
+
}),
|
|
1209
|
+
origin
|
|
1210
|
+
);
|
|
1211
|
+
} catch (error) {
|
|
1212
|
+
json(
|
|
1213
|
+
response,
|
|
1214
|
+
400,
|
|
1215
|
+
{
|
|
1216
|
+
error: error instanceof Error ? error.message : "Coverage bundle read failed"
|
|
1217
|
+
},
|
|
1218
|
+
origin
|
|
1219
|
+
);
|
|
1220
|
+
}
|
|
1221
|
+
return;
|
|
1222
|
+
}
|
|
1194
1223
|
if (request.method !== "GET" || url.pathname !== "/workspace/read") {
|
|
1195
1224
|
json(response, 404, { error: "Not found" }, origin);
|
|
1196
1225
|
return;
|
|
@@ -1460,20 +1489,364 @@ async function runCodexTask(task, context) {
|
|
|
1460
1489
|
});
|
|
1461
1490
|
return {
|
|
1462
1491
|
generatedFiles: [],
|
|
1492
|
+
blockingReasons: [],
|
|
1463
1493
|
manifestPath: companyManifestPath(ticker),
|
|
1464
1494
|
manifest
|
|
1465
1495
|
};
|
|
1466
1496
|
}
|
|
1467
1497
|
|
|
1468
|
-
// src/executors/
|
|
1469
|
-
import {
|
|
1498
|
+
// src/executors/financialReports.ts
|
|
1499
|
+
import { createHash } from "crypto";
|
|
1500
|
+
import { mkdir as mkdir3, readFile as readFile4, writeFile as writeFile2 } from "fs/promises";
|
|
1470
1501
|
import path6 from "path";
|
|
1502
|
+
import {
|
|
1503
|
+
FinancialReportManifestSchema,
|
|
1504
|
+
assessReportCoverage,
|
|
1505
|
+
downloadCninfoReports,
|
|
1506
|
+
downloadSecReports,
|
|
1507
|
+
primaryMarket,
|
|
1508
|
+
primaryTicker,
|
|
1509
|
+
runNotebookLmFinancialReportsIngest
|
|
1510
|
+
} from "@xdsjs/dossier-financial-reports";
|
|
1471
1511
|
import {
|
|
1472
1512
|
buildCompanyManifest as buildCompanyManifest2,
|
|
1473
1513
|
companyManifestPath as companyManifestPath2,
|
|
1514
|
+
financialReportsIndexPath,
|
|
1515
|
+
resolveInsideWorkspace as resolveInsideWorkspace4
|
|
1516
|
+
} from "@xdsjs/dossierx-workspace";
|
|
1517
|
+
var FinancialReportsRuntimeError = class extends Error {
|
|
1518
|
+
code;
|
|
1519
|
+
step;
|
|
1520
|
+
constructor(code, message, step) {
|
|
1521
|
+
super(message);
|
|
1522
|
+
this.name = "FinancialReportsRuntimeError";
|
|
1523
|
+
this.code = code;
|
|
1524
|
+
this.step = step;
|
|
1525
|
+
}
|
|
1526
|
+
};
|
|
1527
|
+
function defaultFetch() {
|
|
1528
|
+
return async (url, init) => fetch(url, init);
|
|
1529
|
+
}
|
|
1530
|
+
function defaultRenderHtmlToPdf() {
|
|
1531
|
+
return async () => {
|
|
1532
|
+
throw new FinancialReportsRuntimeError(
|
|
1533
|
+
"RUNTIME_NOT_FOUND",
|
|
1534
|
+
"SEC HTML-to-PDF renderer is not configured",
|
|
1535
|
+
"financial_reports.render_html_to_pdf"
|
|
1536
|
+
);
|
|
1537
|
+
};
|
|
1538
|
+
}
|
|
1539
|
+
function requireNotebookLmClient(context) {
|
|
1540
|
+
const client = context.financialReports?.notebookLmClient;
|
|
1541
|
+
if (!client) {
|
|
1542
|
+
throw new FinancialReportsRuntimeError(
|
|
1543
|
+
"RUNTIME_NOT_FOUND",
|
|
1544
|
+
"NotebookLM client is not configured",
|
|
1545
|
+
"financial_reports.notebooklm.resolve"
|
|
1546
|
+
);
|
|
1547
|
+
}
|
|
1548
|
+
return client;
|
|
1549
|
+
}
|
|
1550
|
+
function marketToAppMarket(market) {
|
|
1551
|
+
if (market === "US") {
|
|
1552
|
+
return "us";
|
|
1553
|
+
}
|
|
1554
|
+
if (market === "H") {
|
|
1555
|
+
return "hk";
|
|
1556
|
+
}
|
|
1557
|
+
return "cn";
|
|
1558
|
+
}
|
|
1559
|
+
function sha256Json(value) {
|
|
1560
|
+
return `sha256:${createHash("sha256").update(JSON.stringify(value)).digest("hex")}`;
|
|
1561
|
+
}
|
|
1562
|
+
function writeWorkspaceJson(context, relativePath, value) {
|
|
1563
|
+
return writeWorkspaceBytes(
|
|
1564
|
+
context,
|
|
1565
|
+
relativePath,
|
|
1566
|
+
new TextEncoder().encode(`${JSON.stringify(value, null, 2)}
|
|
1567
|
+
`)
|
|
1568
|
+
);
|
|
1569
|
+
}
|
|
1570
|
+
async function writeWorkspaceBytes(context, relativePath, bytes) {
|
|
1571
|
+
const absolutePath = resolveInsideWorkspace4(context.workspaceRoot, relativePath);
|
|
1572
|
+
await mkdir3(path6.dirname(absolutePath), { recursive: true });
|
|
1573
|
+
if (!context.dryRun) {
|
|
1574
|
+
await writeFile2(absolutePath, bytes);
|
|
1575
|
+
}
|
|
1576
|
+
}
|
|
1577
|
+
function createWorkspaceStorage(context) {
|
|
1578
|
+
return {
|
|
1579
|
+
async writeFile(relativePath, bytes) {
|
|
1580
|
+
await writeWorkspaceBytes(context, relativePath, bytes);
|
|
1581
|
+
}
|
|
1582
|
+
};
|
|
1583
|
+
}
|
|
1584
|
+
function readWorkspaceJson(context, relativePath) {
|
|
1585
|
+
const absolutePath = resolveInsideWorkspace4(context.workspaceRoot, relativePath);
|
|
1586
|
+
return readFile4(absolutePath, "utf8").then((content) => JSON.parse(content));
|
|
1587
|
+
}
|
|
1588
|
+
function parseReportManifest(payload) {
|
|
1589
|
+
const maybeRecord = typeof payload === "object" && payload !== null ? payload : {};
|
|
1590
|
+
if (maybeRecord.schemaVersion === "financial-reports/manifest/v1") {
|
|
1591
|
+
return FinancialReportManifestSchema.parse(payload);
|
|
1592
|
+
}
|
|
1593
|
+
return FinancialReportManifestSchema.parse(maybeRecord.manifest);
|
|
1594
|
+
}
|
|
1595
|
+
async function readReportManifest(context, manifestPath) {
|
|
1596
|
+
try {
|
|
1597
|
+
return parseReportManifest(await readWorkspaceJson(context, manifestPath));
|
|
1598
|
+
} catch (error) {
|
|
1599
|
+
if (error instanceof SyntaxError) {
|
|
1600
|
+
throw new FinancialReportsRuntimeError(
|
|
1601
|
+
"VALIDATION_ERROR",
|
|
1602
|
+
"Financial reports manifest is not valid JSON",
|
|
1603
|
+
"financial_reports.manifest.read"
|
|
1604
|
+
);
|
|
1605
|
+
}
|
|
1606
|
+
throw error;
|
|
1607
|
+
}
|
|
1608
|
+
}
|
|
1609
|
+
function assertManifestIssuerMatchesTaskIssuer(manifestIssuer, taskIssuer) {
|
|
1610
|
+
const mismatches = [];
|
|
1611
|
+
if (manifestIssuer.issuerId !== taskIssuer.issuerId) {
|
|
1612
|
+
mismatches.push("issuerId");
|
|
1613
|
+
}
|
|
1614
|
+
if (manifestIssuer.companyId !== taskIssuer.companyId) {
|
|
1615
|
+
mismatches.push("companyId");
|
|
1616
|
+
}
|
|
1617
|
+
if (primaryTicker(manifestIssuer) !== primaryTicker(taskIssuer)) {
|
|
1618
|
+
mismatches.push("primary ticker");
|
|
1619
|
+
}
|
|
1620
|
+
if (primaryMarket(manifestIssuer) !== primaryMarket(taskIssuer)) {
|
|
1621
|
+
mismatches.push("primary market");
|
|
1622
|
+
}
|
|
1623
|
+
if (mismatches.length > 0) {
|
|
1624
|
+
throw new FinancialReportsRuntimeError(
|
|
1625
|
+
"VALIDATION_ERROR",
|
|
1626
|
+
`Financial reports manifest issuer does not match task payload issuer: ${mismatches.join(", ")}`,
|
|
1627
|
+
"financial_reports.manifest.validate"
|
|
1628
|
+
);
|
|
1629
|
+
}
|
|
1630
|
+
}
|
|
1631
|
+
function financialTaskResult(input) {
|
|
1632
|
+
const ticker = primaryTicker(input.manifest.issuer);
|
|
1633
|
+
const coverage = assessReportCoverage(input.manifest);
|
|
1634
|
+
return {
|
|
1635
|
+
schemaVersion: "financial-reports/task-result/v1",
|
|
1636
|
+
issuerId: input.manifest.issuer.issuerId,
|
|
1637
|
+
companyId: input.manifest.issuer.companyId,
|
|
1638
|
+
ticker,
|
|
1639
|
+
manifestPath: input.manifestPath,
|
|
1640
|
+
manifestHash: input.manifestHash,
|
|
1641
|
+
notebookPath: input.notebookPath,
|
|
1642
|
+
factsBundlePath: input.factsBundlePath,
|
|
1643
|
+
reportCount: input.manifest.reports.length,
|
|
1644
|
+
coverageStatus: coverage.status,
|
|
1645
|
+
blockingReasons: coverage.knownGaps
|
|
1646
|
+
};
|
|
1647
|
+
}
|
|
1648
|
+
async function runSyncReports(task, context) {
|
|
1649
|
+
const issuer = task.payload.issuer;
|
|
1650
|
+
const market = primaryMarket(issuer);
|
|
1651
|
+
const fetch2 = context.financialReports?.fetch ?? defaultFetch();
|
|
1652
|
+
const storage = createWorkspaceStorage(context);
|
|
1653
|
+
const now = context.financialReports?.now;
|
|
1654
|
+
let result;
|
|
1655
|
+
await context.appendEvent({
|
|
1656
|
+
level: "info",
|
|
1657
|
+
message: "Starting financial reports sync",
|
|
1658
|
+
data: { issuerId: issuer.issuerId, market }
|
|
1659
|
+
});
|
|
1660
|
+
if (market === "US") {
|
|
1661
|
+
result = await downloadSecReports({
|
|
1662
|
+
issuer,
|
|
1663
|
+
fiscalYears: task.payload.fiscalYears,
|
|
1664
|
+
reportTypes: task.payload.reportTypes,
|
|
1665
|
+
fetch: fetch2,
|
|
1666
|
+
storage,
|
|
1667
|
+
renderHtmlToPdf: context.financialReports?.renderHtmlToPdf ?? defaultRenderHtmlToPdf(),
|
|
1668
|
+
now
|
|
1669
|
+
});
|
|
1670
|
+
} else {
|
|
1671
|
+
result = await downloadCninfoReports({
|
|
1672
|
+
issuer,
|
|
1673
|
+
fiscalYears: task.payload.fiscalYears,
|
|
1674
|
+
reportTypes: task.payload.reportTypes,
|
|
1675
|
+
fetch: fetch2,
|
|
1676
|
+
storage,
|
|
1677
|
+
now
|
|
1678
|
+
});
|
|
1679
|
+
}
|
|
1680
|
+
const ticker = primaryTicker(result.manifest.issuer);
|
|
1681
|
+
const reportIndexPath = financialReportsIndexPath(ticker);
|
|
1682
|
+
const manifestHash = sha256Json(result.manifest);
|
|
1683
|
+
const reportIndex = {
|
|
1684
|
+
schemaVersion: "financial-reports/report-index/v1",
|
|
1685
|
+
generatedAt: result.manifest.generatedAt,
|
|
1686
|
+
issuer: result.manifest.issuer,
|
|
1687
|
+
manifestHash,
|
|
1688
|
+
manifest: result.manifest,
|
|
1689
|
+
coverage: result.coverage,
|
|
1690
|
+
skippedReports: result.skippedReports,
|
|
1691
|
+
missingReports: result.missingReports
|
|
1692
|
+
};
|
|
1693
|
+
await writeWorkspaceJson(context, reportIndexPath, reportIndex);
|
|
1694
|
+
const companyManifest = context.dryRun ? void 0 : await buildCompanyManifest2({
|
|
1695
|
+
workspaceRoot: context.workspaceRoot,
|
|
1696
|
+
ticker,
|
|
1697
|
+
market: marketToAppMarket(market)
|
|
1698
|
+
});
|
|
1699
|
+
await context.appendEvent({
|
|
1700
|
+
level: "info",
|
|
1701
|
+
message: "Financial reports sync completed",
|
|
1702
|
+
data: {
|
|
1703
|
+
issuerId: issuer.issuerId,
|
|
1704
|
+
coverageStatus: result.coverage.status,
|
|
1705
|
+
reportCount: result.manifest.reports.length,
|
|
1706
|
+
manifestPath: reportIndexPath
|
|
1707
|
+
}
|
|
1708
|
+
});
|
|
1709
|
+
return {
|
|
1710
|
+
generatedFiles: [
|
|
1711
|
+
...result.manifest.reports.map((report) => report.localPath),
|
|
1712
|
+
reportIndexPath,
|
|
1713
|
+
companyManifestPath2(ticker)
|
|
1714
|
+
],
|
|
1715
|
+
manifestPath: companyManifestPath2(ticker),
|
|
1716
|
+
manifest: companyManifest,
|
|
1717
|
+
blockingReasons: [],
|
|
1718
|
+
financialReports: financialTaskResult({
|
|
1719
|
+
manifest: result.manifest,
|
|
1720
|
+
manifestPath: reportIndexPath,
|
|
1721
|
+
manifestHash
|
|
1722
|
+
})
|
|
1723
|
+
};
|
|
1724
|
+
}
|
|
1725
|
+
async function runNotebookIngest(task, context) {
|
|
1726
|
+
const client = requireNotebookLmClient(context);
|
|
1727
|
+
const manifest = await readReportManifest(context, task.payload.manifestPath);
|
|
1728
|
+
assertManifestIssuerMatchesTaskIssuer(manifest.issuer, task.payload.issuer);
|
|
1729
|
+
const ticker = primaryTicker(manifest.issuer);
|
|
1730
|
+
const manifestHash = sha256Json(manifest);
|
|
1731
|
+
const factsAsOf = task.payload.factsAsOf ?? (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
1732
|
+
await context.appendEvent({
|
|
1733
|
+
level: "info",
|
|
1734
|
+
message: "Starting NotebookLM financial reports ingest",
|
|
1735
|
+
data: { issuerId: manifest.issuer.issuerId, manifestPath: task.payload.manifestPath }
|
|
1736
|
+
});
|
|
1737
|
+
const ingest = await runNotebookLmFinancialReportsIngest({
|
|
1738
|
+
manifest,
|
|
1739
|
+
client,
|
|
1740
|
+
manifestHash,
|
|
1741
|
+
factsAsOf,
|
|
1742
|
+
generatedAt: context.financialReports?.now?.(),
|
|
1743
|
+
notebookTitle: task.payload.notebookTitle
|
|
1744
|
+
});
|
|
1745
|
+
const notebookPath = `companies/${ticker}/financial-reports/notebooklm/notebook.json`;
|
|
1746
|
+
const factsBundlePath = `companies/${ticker}/financial-reports/ingest/facts-bundle.json`;
|
|
1747
|
+
await writeWorkspaceJson(context, notebookPath, {
|
|
1748
|
+
schemaVersion: "financial-reports/notebooklm-run/v1",
|
|
1749
|
+
generatedAt: ingest.factsBundle.generatedAt,
|
|
1750
|
+
manifestPath: task.payload.manifestPath,
|
|
1751
|
+
notebooklm: ingest.notebooklm,
|
|
1752
|
+
sourceIds: ingest.sourceIds
|
|
1753
|
+
});
|
|
1754
|
+
await writeWorkspaceJson(context, factsBundlePath, ingest.factsBundle);
|
|
1755
|
+
const companyManifest = context.dryRun ? void 0 : await buildCompanyManifest2({
|
|
1756
|
+
workspaceRoot: context.workspaceRoot,
|
|
1757
|
+
ticker,
|
|
1758
|
+
market: marketToAppMarket(primaryMarket(manifest.issuer))
|
|
1759
|
+
});
|
|
1760
|
+
await context.appendEvent({
|
|
1761
|
+
level: "info",
|
|
1762
|
+
message: "NotebookLM financial reports ingest completed",
|
|
1763
|
+
data: {
|
|
1764
|
+
issuerId: manifest.issuer.issuerId,
|
|
1765
|
+
notebookPath,
|
|
1766
|
+
factsBundlePath
|
|
1767
|
+
}
|
|
1768
|
+
});
|
|
1769
|
+
return {
|
|
1770
|
+
generatedFiles: [notebookPath, factsBundlePath, companyManifestPath2(ticker)],
|
|
1771
|
+
manifestPath: companyManifestPath2(ticker),
|
|
1772
|
+
manifest: companyManifest,
|
|
1773
|
+
blockingReasons: [],
|
|
1774
|
+
financialReports: financialTaskResult({
|
|
1775
|
+
manifest,
|
|
1776
|
+
manifestPath: task.payload.manifestPath,
|
|
1777
|
+
manifestHash,
|
|
1778
|
+
notebookPath,
|
|
1779
|
+
factsBundlePath
|
|
1780
|
+
})
|
|
1781
|
+
};
|
|
1782
|
+
}
|
|
1783
|
+
async function runRefreshCompany(task, context) {
|
|
1784
|
+
const syncResult = await runSyncReports(
|
|
1785
|
+
{
|
|
1786
|
+
id: task.id,
|
|
1787
|
+
type: "financial_reports.sync_reports",
|
|
1788
|
+
payload: {
|
|
1789
|
+
issuer: task.payload.issuer,
|
|
1790
|
+
fiscalYears: task.payload.fiscalYears,
|
|
1791
|
+
reportTypes: task.payload.reportTypes
|
|
1792
|
+
}
|
|
1793
|
+
},
|
|
1794
|
+
context
|
|
1795
|
+
);
|
|
1796
|
+
if (!context.financialReports?.notebookLmClient) {
|
|
1797
|
+
return syncResult;
|
|
1798
|
+
}
|
|
1799
|
+
const manifestPath = syncResult.financialReports?.manifestPath;
|
|
1800
|
+
if (!manifestPath) {
|
|
1801
|
+
return syncResult;
|
|
1802
|
+
}
|
|
1803
|
+
const ingestResult = await runNotebookIngest(
|
|
1804
|
+
{
|
|
1805
|
+
id: task.id,
|
|
1806
|
+
type: "financial_reports.ingest_notebook",
|
|
1807
|
+
payload: {
|
|
1808
|
+
issuer: task.payload.issuer,
|
|
1809
|
+
manifestPath,
|
|
1810
|
+
factsAsOf: task.payload.factsAsOf,
|
|
1811
|
+
notebookTitle: task.payload.notebookTitle
|
|
1812
|
+
}
|
|
1813
|
+
},
|
|
1814
|
+
context
|
|
1815
|
+
);
|
|
1816
|
+
return {
|
|
1817
|
+
...ingestResult,
|
|
1818
|
+
generatedFiles: [
|
|
1819
|
+
...syncResult.generatedFiles,
|
|
1820
|
+
...ingestResult.generatedFiles
|
|
1821
|
+
]
|
|
1822
|
+
};
|
|
1823
|
+
}
|
|
1824
|
+
async function runFinancialReportsTask(task, context) {
|
|
1825
|
+
if (task.type === "financial_reports.sync_reports") {
|
|
1826
|
+
return runSyncReports(task, context);
|
|
1827
|
+
}
|
|
1828
|
+
if (task.type === "financial_reports.ingest_notebook" || task.type === "financial_reports.extract_facts") {
|
|
1829
|
+
return runNotebookIngest(task, context);
|
|
1830
|
+
}
|
|
1831
|
+
if (task.type === "financial_reports.refresh_company") {
|
|
1832
|
+
return runRefreshCompany(task, context);
|
|
1833
|
+
}
|
|
1834
|
+
throw new FinancialReportsRuntimeError(
|
|
1835
|
+
"VALIDATION_ERROR",
|
|
1836
|
+
"Unsupported financial reports task type",
|
|
1837
|
+
"financial_reports.dispatch"
|
|
1838
|
+
);
|
|
1839
|
+
}
|
|
1840
|
+
|
|
1841
|
+
// src/executors/investWiki.ts
|
|
1842
|
+
import { access as access2, mkdir as mkdir4, readFile as readFile5, stat as stat3, writeFile as writeFile3 } from "fs/promises";
|
|
1843
|
+
import path7 from "path";
|
|
1844
|
+
import {
|
|
1845
|
+
buildCompanyManifest as buildCompanyManifest3,
|
|
1846
|
+
companyManifestPath as companyManifestPath3,
|
|
1474
1847
|
investWikiConfigPath,
|
|
1475
1848
|
investWikiRoot as investWikiRoot2,
|
|
1476
|
-
resolveInsideWorkspace as
|
|
1849
|
+
resolveInsideWorkspace as resolveInsideWorkspace5
|
|
1477
1850
|
} from "@xdsjs/dossierx-workspace";
|
|
1478
1851
|
async function exists(absolutePath) {
|
|
1479
1852
|
try {
|
|
@@ -1498,7 +1871,7 @@ function isWorkspaceGuardError2(error) {
|
|
|
1498
1871
|
}
|
|
1499
1872
|
function workspacePath2(workspaceRoot, relativePath, step) {
|
|
1500
1873
|
try {
|
|
1501
|
-
return
|
|
1874
|
+
return resolveInsideWorkspace5(workspaceRoot, relativePath);
|
|
1502
1875
|
} catch (error) {
|
|
1503
1876
|
if (isWorkspaceGuardError2(error)) {
|
|
1504
1877
|
throw new InvestWikiRuntimeError(
|
|
@@ -1579,11 +1952,11 @@ function isMarket(value) {
|
|
|
1579
1952
|
async function readManifestMarket(workspaceRoot, ticker) {
|
|
1580
1953
|
const manifestPath = workspacePath2(
|
|
1581
1954
|
workspaceRoot,
|
|
1582
|
-
|
|
1955
|
+
companyManifestPath3(ticker),
|
|
1583
1956
|
"invest_wiki.sync"
|
|
1584
1957
|
);
|
|
1585
1958
|
try {
|
|
1586
|
-
const manifest = JSON.parse(await
|
|
1959
|
+
const manifest = JSON.parse(await readFile5(manifestPath, "utf8"));
|
|
1587
1960
|
if (!isMarket(manifest.market)) {
|
|
1588
1961
|
throw new InvestWikiRuntimeError(
|
|
1589
1962
|
"VALIDATION_ERROR",
|
|
@@ -1626,7 +1999,7 @@ async function runInitCompanyVault(task, context) {
|
|
|
1626
1999
|
`${vaultRelativePath}/.llm-wiki-invest/dossier-state.json`,
|
|
1627
2000
|
"invest_wiki.init_company_vault"
|
|
1628
2001
|
);
|
|
1629
|
-
await
|
|
2002
|
+
await mkdir4(vaultRoot, { recursive: true });
|
|
1630
2003
|
await context.appendEvent({
|
|
1631
2004
|
level: "info",
|
|
1632
2005
|
message: "Initializing invest-wiki vault",
|
|
@@ -1645,10 +2018,10 @@ async function runInitCompanyVault(task, context) {
|
|
|
1645
2018
|
const statusOutput = await runner.run(["dossier", "status"], { cwd: vaultRoot });
|
|
1646
2019
|
await appendOutputEvent2(context, "Invest wiki dossier status completed", statusOutput);
|
|
1647
2020
|
if (!await exists(markerPath)) {
|
|
1648
|
-
await
|
|
1649
|
-
await
|
|
2021
|
+
await mkdir4(path7.dirname(markerPath), { recursive: true });
|
|
2022
|
+
await writeFile3(markerPath, "# Created by dossierx-daemon\n");
|
|
1650
2023
|
}
|
|
1651
|
-
const manifest = await
|
|
2024
|
+
const manifest = await buildCompanyManifest3({
|
|
1652
2025
|
workspaceRoot: context.workspaceRoot,
|
|
1653
2026
|
ticker,
|
|
1654
2027
|
market: task.payload.market
|
|
@@ -1662,9 +2035,10 @@ async function runInitCompanyVault(task, context) {
|
|
|
1662
2035
|
generatedFiles: [
|
|
1663
2036
|
vaultRelativePath,
|
|
1664
2037
|
markerRelativePath,
|
|
1665
|
-
|
|
2038
|
+
companyManifestPath3(ticker)
|
|
1666
2039
|
],
|
|
1667
|
-
|
|
2040
|
+
blockingReasons: [],
|
|
2041
|
+
manifestPath: companyManifestPath3(ticker),
|
|
1668
2042
|
manifest
|
|
1669
2043
|
};
|
|
1670
2044
|
}
|
|
@@ -1688,7 +2062,8 @@ async function runStatus(task, context) {
|
|
|
1688
2062
|
);
|
|
1689
2063
|
return {
|
|
1690
2064
|
generatedFiles: [],
|
|
1691
|
-
|
|
2065
|
+
blockingReasons: [],
|
|
2066
|
+
manifestPath: companyManifestPath3(ticker)
|
|
1692
2067
|
};
|
|
1693
2068
|
}
|
|
1694
2069
|
async function runSync(task, context) {
|
|
@@ -1703,14 +2078,15 @@ async function runSync(task, context) {
|
|
|
1703
2078
|
const args = task.payload.dryRun ? ["sync", "--dry-run"] : ["sync"];
|
|
1704
2079
|
const output = await runner.run(args, { cwd: vaultRoot });
|
|
1705
2080
|
await appendOutputEvent2(context, "Invest wiki sync completed", output);
|
|
1706
|
-
const manifest = await
|
|
2081
|
+
const manifest = await buildCompanyManifest3({
|
|
1707
2082
|
workspaceRoot: context.workspaceRoot,
|
|
1708
2083
|
ticker,
|
|
1709
2084
|
market
|
|
1710
2085
|
});
|
|
1711
2086
|
return {
|
|
1712
2087
|
generatedFiles: [],
|
|
1713
|
-
|
|
2088
|
+
blockingReasons: [],
|
|
2089
|
+
manifestPath: companyManifestPath3(ticker),
|
|
1714
2090
|
manifest
|
|
1715
2091
|
};
|
|
1716
2092
|
}
|
|
@@ -1732,13 +2108,13 @@ async function runInvestWikiTask(task, context) {
|
|
|
1732
2108
|
}
|
|
1733
2109
|
|
|
1734
2110
|
// src/executors/mockWriteCompanyReport.ts
|
|
1735
|
-
import { mkdir as
|
|
1736
|
-
import
|
|
2111
|
+
import { mkdir as mkdir5, writeFile as writeFile4 } from "fs/promises";
|
|
2112
|
+
import path8 from "path";
|
|
1737
2113
|
import {
|
|
1738
|
-
buildCompanyManifest as
|
|
1739
|
-
companyManifestPath as
|
|
2114
|
+
buildCompanyManifest as buildCompanyManifest4,
|
|
2115
|
+
companyManifestPath as companyManifestPath4,
|
|
1740
2116
|
companyRoot,
|
|
1741
|
-
resolveInsideWorkspace as
|
|
2117
|
+
resolveInsideWorkspace as resolveInsideWorkspace6,
|
|
1742
2118
|
rightBusinessPath,
|
|
1743
2119
|
rightPeoplePath,
|
|
1744
2120
|
rightPricePath
|
|
@@ -1772,10 +2148,10 @@ var rightPrice = (ticker) => `# Right Price - ${ticker}
|
|
|
1772
2148
|
This is a placeholder right-price analysis for ${ticker}.
|
|
1773
2149
|
`;
|
|
1774
2150
|
async function writeWorkspaceFile(context, relativePath, content) {
|
|
1775
|
-
const absolutePath =
|
|
1776
|
-
await
|
|
2151
|
+
const absolutePath = resolveInsideWorkspace6(context.workspaceRoot, relativePath);
|
|
2152
|
+
await mkdir5(path8.dirname(absolutePath), { recursive: true });
|
|
1777
2153
|
if (!context.dryRun) {
|
|
1778
|
-
await
|
|
2154
|
+
await writeFile4(absolutePath, content);
|
|
1779
2155
|
}
|
|
1780
2156
|
}
|
|
1781
2157
|
async function runMockWriteCompanyReport(task, context) {
|
|
@@ -1790,7 +2166,7 @@ async function runMockWriteCompanyReport(task, context) {
|
|
|
1790
2166
|
message: "Creating company directory",
|
|
1791
2167
|
data: { ticker }
|
|
1792
2168
|
});
|
|
1793
|
-
await
|
|
2169
|
+
await mkdir5(resolveInsideWorkspace6(context.workspaceRoot, companyRoot(ticker)), {
|
|
1794
2170
|
recursive: true
|
|
1795
2171
|
});
|
|
1796
2172
|
await context.appendEvent({
|
|
@@ -1814,11 +2190,11 @@ async function runMockWriteCompanyReport(task, context) {
|
|
|
1814
2190
|
await context.appendEvent({
|
|
1815
2191
|
level: "info",
|
|
1816
2192
|
message: "Generating manifest.json",
|
|
1817
|
-
data: { path:
|
|
2193
|
+
data: { path: companyManifestPath4(ticker) }
|
|
1818
2194
|
});
|
|
1819
2195
|
let manifest;
|
|
1820
2196
|
if (!context.dryRun) {
|
|
1821
|
-
manifest = await
|
|
2197
|
+
manifest = await buildCompanyManifest4({
|
|
1822
2198
|
workspaceRoot: context.workspaceRoot,
|
|
1823
2199
|
ticker,
|
|
1824
2200
|
market: task.payload.market
|
|
@@ -1830,8 +2206,9 @@ async function runMockWriteCompanyReport(task, context) {
|
|
|
1830
2206
|
data: { ticker }
|
|
1831
2207
|
});
|
|
1832
2208
|
return {
|
|
1833
|
-
generatedFiles: [...generatedFiles,
|
|
1834
|
-
|
|
2209
|
+
generatedFiles: [...generatedFiles, companyManifestPath4(ticker)],
|
|
2210
|
+
blockingReasons: [],
|
|
2211
|
+
manifestPath: companyManifestPath4(ticker),
|
|
1835
2212
|
manifest
|
|
1836
2213
|
};
|
|
1837
2214
|
}
|
|
@@ -1844,6 +2221,9 @@ function getExecutor(task) {
|
|
|
1844
2221
|
if (task.type.startsWith("invest_wiki.")) {
|
|
1845
2222
|
return runInvestWikiTask;
|
|
1846
2223
|
}
|
|
2224
|
+
if (task.type.startsWith("financial_reports.")) {
|
|
2225
|
+
return runFinancialReportsTask;
|
|
2226
|
+
}
|
|
1847
2227
|
if (task.type === "mock.write_company_report") {
|
|
1848
2228
|
return runMockWriteCompanyReport;
|
|
1849
2229
|
}
|
|
@@ -1912,6 +2292,7 @@ async function runTask(ctx, task, agent) {
|
|
|
1912
2292
|
dryRun: ctx.dryRun,
|
|
1913
2293
|
codex: codexRunnerForTask(ctx, agent),
|
|
1914
2294
|
investWiki: ctx.investWiki,
|
|
2295
|
+
financialReports: ctx.financialReports,
|
|
1915
2296
|
appendEvent: async (event) => {
|
|
1916
2297
|
await appendLocalEvent(ctx, task.id, TaskEventInputSchema.parse(event));
|
|
1917
2298
|
},
|
|
@@ -1979,6 +2360,22 @@ async function runTask(ctx, task, agent) {
|
|
|
1979
2360
|
});
|
|
1980
2361
|
return;
|
|
1981
2362
|
}
|
|
2363
|
+
if (error instanceof FinancialReportsRuntimeError) {
|
|
2364
|
+
await appendLocalEvent(ctx, task.id, {
|
|
2365
|
+
level: "error",
|
|
2366
|
+
message: error.message,
|
|
2367
|
+
data: { code: error.code, step: error.step }
|
|
2368
|
+
});
|
|
2369
|
+
await ctx.api.failTask(task.id, {
|
|
2370
|
+
error: {
|
|
2371
|
+
code: error.code,
|
|
2372
|
+
message: error.message,
|
|
2373
|
+
step: error.step,
|
|
2374
|
+
recoverable: false
|
|
2375
|
+
}
|
|
2376
|
+
});
|
|
2377
|
+
return;
|
|
2378
|
+
}
|
|
1982
2379
|
if (error instanceof GitMirrorError) {
|
|
1983
2380
|
await appendLocalEvent(ctx, task.id, {
|
|
1984
2381
|
level: "error",
|
|
@@ -2100,6 +2497,9 @@ async function detectCapabilities() {
|
|
|
2100
2497
|
git,
|
|
2101
2498
|
node: true,
|
|
2102
2499
|
python: python3 || python,
|
|
2500
|
+
financialReports: true,
|
|
2501
|
+
financialReportsNotebookLm: false,
|
|
2502
|
+
financialReportsSecHtmlRenderer: false,
|
|
2103
2503
|
investWikiRuntime,
|
|
2104
2504
|
codex,
|
|
2105
2505
|
claude
|
|
@@ -2176,9 +2576,9 @@ async function subscribeToTaskAvailable(options, onEvent, onInvalidEvent) {
|
|
|
2176
2576
|
}
|
|
2177
2577
|
|
|
2178
2578
|
// src/service.ts
|
|
2179
|
-
import { mkdir as
|
|
2579
|
+
import { mkdir as mkdir6, unlink, writeFile as writeFile5 } from "fs/promises";
|
|
2180
2580
|
import os2 from "os";
|
|
2181
|
-
import
|
|
2581
|
+
import path9 from "path";
|
|
2182
2582
|
var LAUNCH_AGENT_LABEL = "com.xdsjs.dossierx-daemon";
|
|
2183
2583
|
function xmlEscape(value) {
|
|
2184
2584
|
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
@@ -2187,10 +2587,10 @@ function shellQuote(value) {
|
|
|
2187
2587
|
return `'${value.replaceAll("'", "'\\''")}'`;
|
|
2188
2588
|
}
|
|
2189
2589
|
function launchAgentsDir() {
|
|
2190
|
-
return
|
|
2590
|
+
return path9.join(os2.homedir(), "Library", "LaunchAgents");
|
|
2191
2591
|
}
|
|
2192
2592
|
function launchAgentPlistPath(label = LAUNCH_AGENT_LABEL, dir = launchAgentsDir()) {
|
|
2193
|
-
return
|
|
2593
|
+
return path9.join(dir, `${label}.plist`);
|
|
2194
2594
|
}
|
|
2195
2595
|
function resolveDaemonProgramArguments(input) {
|
|
2196
2596
|
if (input.daemonCommand?.trim()) {
|
|
@@ -2201,14 +2601,14 @@ function resolveDaemonProgramArguments(input) {
|
|
|
2201
2601
|
if (!entry) {
|
|
2202
2602
|
throw new Error("Unable to resolve daemon entrypoint for LaunchAgent");
|
|
2203
2603
|
}
|
|
2204
|
-
if (
|
|
2604
|
+
if (path9.extname(entry) === ".ts") {
|
|
2205
2605
|
throw new Error(
|
|
2206
2606
|
"LaunchAgent cannot run a TypeScript daemon entrypoint directly; pass --daemon-command when installing from source"
|
|
2207
2607
|
);
|
|
2208
2608
|
}
|
|
2209
2609
|
return [
|
|
2210
2610
|
input.execPath ?? process.execPath,
|
|
2211
|
-
|
|
2611
|
+
path9.resolve(entry),
|
|
2212
2612
|
"--log-level",
|
|
2213
2613
|
input.logLevel ?? "info"
|
|
2214
2614
|
];
|
|
@@ -2267,7 +2667,7 @@ async function installLaunchAgent(options = {}) {
|
|
|
2267
2667
|
throw new Error("DossierX LaunchAgent service is only supported on macOS");
|
|
2268
2668
|
}
|
|
2269
2669
|
const label = options.label ?? LAUNCH_AGENT_LABEL;
|
|
2270
|
-
const configDir =
|
|
2670
|
+
const configDir = path9.resolve(
|
|
2271
2671
|
expandHomePath(options.configDir ?? getDaemonConfigDir())
|
|
2272
2672
|
);
|
|
2273
2673
|
const config = await readDaemonLocalConfig(configDir);
|
|
@@ -2287,8 +2687,8 @@ async function installLaunchAgent(options = {}) {
|
|
|
2287
2687
|
);
|
|
2288
2688
|
}
|
|
2289
2689
|
const plistPath = launchAgentPlistPath(label, options.launchAgentsDir);
|
|
2290
|
-
const stdoutPath =
|
|
2291
|
-
const stderrPath =
|
|
2690
|
+
const stdoutPath = path9.join(configDir, "daemon.out.log");
|
|
2691
|
+
const stderrPath = path9.join(configDir, "daemon.err.log");
|
|
2292
2692
|
const uid = typeof process.getuid === "function" ? process.getuid() : "<uid>";
|
|
2293
2693
|
const programArguments = resolveDaemonProgramArguments({
|
|
2294
2694
|
daemonCommand: options.daemonCommand,
|
|
@@ -2296,9 +2696,9 @@ async function installLaunchAgent(options = {}) {
|
|
|
2296
2696
|
argv: options.argv,
|
|
2297
2697
|
execPath: options.execPath
|
|
2298
2698
|
});
|
|
2299
|
-
await
|
|
2300
|
-
await
|
|
2301
|
-
await
|
|
2699
|
+
await mkdir6(path9.dirname(plistPath), { recursive: true });
|
|
2700
|
+
await mkdir6(configDir, { recursive: true, mode: 448 });
|
|
2701
|
+
await writeFile5(
|
|
2302
2702
|
plistPath,
|
|
2303
2703
|
buildLaunchAgentPlist({
|
|
2304
2704
|
label,
|
|
@@ -2339,7 +2739,7 @@ async function uninstallLaunchAgent(options = {}) {
|
|
|
2339
2739
|
|
|
2340
2740
|
// src/cli.ts
|
|
2341
2741
|
async function ensureWorkspaceDirectory(workspace) {
|
|
2342
|
-
await
|
|
2742
|
+
await mkdir7(workspace, { recursive: true }).catch(async (error) => {
|
|
2343
2743
|
const stats2 = await stat4(workspace).catch(() => null);
|
|
2344
2744
|
if (!stats2?.isDirectory()) {
|
|
2345
2745
|
throw new Error("Workspace path is not a directory");
|
|
@@ -2437,7 +2837,7 @@ async function runDaemon(options) {
|
|
|
2437
2837
|
"Missing daemon connection config. Run the generated daemon command from DossierX first."
|
|
2438
2838
|
);
|
|
2439
2839
|
}
|
|
2440
|
-
const workspaceRoot =
|
|
2840
|
+
const workspaceRoot = path10.resolve(expandHomePath(workspacePath3));
|
|
2441
2841
|
await ensureWorkspaceDirectory(workspaceRoot);
|
|
2442
2842
|
const capabilities = await detectCapabilities();
|
|
2443
2843
|
const investWiki = createInvestWikiRunnerFromOptions(runtimeOptions);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xdsjs/dossierx-daemon",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -28,9 +28,10 @@
|
|
|
28
28
|
"execa": "^9.0.0",
|
|
29
29
|
"pino": "^10.0.0",
|
|
30
30
|
"zod": "^4.0.0",
|
|
31
|
-
"@xdsjs/
|
|
32
|
-
"@xdsjs/dossierx-
|
|
33
|
-
"@xdsjs/dossierx-shared": "^0.1.
|
|
31
|
+
"@xdsjs/dossier-financial-reports": "^0.1.0",
|
|
32
|
+
"@xdsjs/dossierx-git-mirror": "^0.1.2",
|
|
33
|
+
"@xdsjs/dossierx-shared": "^0.1.3",
|
|
34
|
+
"@xdsjs/dossierx-workspace": "^0.1.1"
|
|
34
35
|
},
|
|
35
36
|
"devDependencies": {
|
|
36
37
|
"@types/node": "^24.0.0",
|