@xdsjs/dossierx-daemon 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.
Files changed (2) hide show
  1. package/dist/index.js +191 -95
  2. package/package.json +4 -3
package/dist/index.js CHANGED
@@ -1,9 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/cli.ts
4
- import { stat as stat4 } from "fs/promises";
4
+ import { mkdir as mkdir6, stat as stat4 } from "fs/promises";
5
5
  import os3 from "os";
6
- import path8 from "path";
6
+ import path9 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(path9, body, schema) {
34
- const response = await this.fetchImpl(`${this.serverUrl}${path9}`, {
33
+ async post(path10, body, schema) {
34
+ const response = await this.fetchImpl(`${this.serverUrl}${path10}`, {
35
35
  method: "POST",
36
36
  headers: {
37
37
  authorization: `Bearer ${this.machineKey}`,
@@ -495,9 +495,103 @@ function createCodexRunnerFromOptions(options, env = process.env) {
495
495
  });
496
496
  }
497
497
 
498
+ // src/git-mirror.ts
499
+ import path2 from "path";
500
+ import {
501
+ publishCompanySnapshot
502
+ } from "@xdsjs/dossierx-git-mirror";
503
+
504
+ // src/local-config.ts
505
+ import { chmod, mkdir, readFile, writeFile } from "fs/promises";
506
+ import os from "os";
507
+ import path from "path";
508
+ import { z } from "zod";
509
+ import { DOSSIERX_CONFIG_DIR } from "@xdsjs/dossierx-workspace";
510
+ var DaemonLocalConfigSchema = z.object({
511
+ machineId: z.string().uuid().optional(),
512
+ machineKey: z.string().startsWith("dx_machine_"),
513
+ serverUrl: z.string().min(1),
514
+ supabaseUrl: z.string().min(1),
515
+ supabaseAnonKey: z.string().min(1),
516
+ workspacePath: z.string().min(1),
517
+ investWikiMode: z.string().optional(),
518
+ investWikiLocalRepo: z.string().optional(),
519
+ investWikiCommand: z.string().optional(),
520
+ codexCommand: z.string().optional(),
521
+ codexModel: z.string().optional(),
522
+ codexSandbox: z.string().optional(),
523
+ gitMirrorRoot: z.string().optional(),
524
+ gitMirrorRemote: z.string().optional(),
525
+ gitMirrorBranch: z.string().optional(),
526
+ gitCommand: z.string().optional(),
527
+ localApiPort: z.number().int().positive().optional()
528
+ });
529
+ function expandHomePath(input) {
530
+ if (input === "~") {
531
+ return os.homedir();
532
+ }
533
+ if (input.startsWith("~/")) {
534
+ return path.join(os.homedir(), input.slice(2));
535
+ }
536
+ return input;
537
+ }
538
+ function getDaemonConfigDir(env = process.env) {
539
+ return path.resolve(
540
+ expandHomePath(env.DOSSIERX_CONFIG_DIR ?? DOSSIERX_CONFIG_DIR)
541
+ );
542
+ }
543
+ function daemonConfigPath(configDir = getDaemonConfigDir()) {
544
+ return path.join(configDir, "config.json");
545
+ }
546
+ async function readDaemonLocalConfig(configDir = getDaemonConfigDir()) {
547
+ try {
548
+ return DaemonLocalConfigSchema.parse(
549
+ JSON.parse(await readFile(daemonConfigPath(configDir), "utf8"))
550
+ );
551
+ } catch (error) {
552
+ if (error && typeof error === "object" && "code" in error && error.code === "ENOENT") {
553
+ return null;
554
+ }
555
+ throw error;
556
+ }
557
+ }
558
+ async function writeDaemonLocalConfig(config, configDir = getDaemonConfigDir()) {
559
+ const parsed = DaemonLocalConfigSchema.parse(config);
560
+ await mkdir(configDir, { recursive: true, mode: 448 });
561
+ const target = daemonConfigPath(configDir);
562
+ await writeFile(target, `${JSON.stringify(parsed, null, 2)}
563
+ `, {
564
+ mode: 384
565
+ });
566
+ await chmod(target, 384);
567
+ }
568
+
569
+ // src/git-mirror.ts
570
+ function createGitMirrorFromOptions(options) {
571
+ if (!options.gitMirrorRoot && !options.gitMirrorRemote) {
572
+ return null;
573
+ }
574
+ const mirrorRoot = path2.resolve(
575
+ expandHomePath(
576
+ options.gitMirrorRoot ?? path2.join(getDaemonConfigDir(), "git-mirror")
577
+ )
578
+ );
579
+ return {
580
+ publishCompanySnapshot(input) {
581
+ return publishCompanySnapshot({
582
+ ...input,
583
+ mirrorRoot,
584
+ remoteUrl: options.gitMirrorRemote,
585
+ branch: options.gitMirrorBranch,
586
+ gitCommand: options.gitCommand
587
+ });
588
+ }
589
+ };
590
+ }
591
+
498
592
  // src/invest-wiki/runner.ts
499
593
  import { access } from "fs/promises";
500
- import path from "path";
594
+ import path3 from "path";
501
595
  import { execa as execa2 } from "execa";
502
596
 
503
597
  // src/invest-wiki/config.ts
@@ -591,7 +685,7 @@ function createLocalRepoRunner(config) {
591
685
  return {
592
686
  kind: "local-repo",
593
687
  async run(args, options) {
594
- const cliPath = path.join(config.localRepo, "dist", "cli.js");
688
+ const cliPath = path3.join(config.localRepo, "dist", "cli.js");
595
689
  await assertFileExists(cliPath);
596
690
  const result = await runInvestWikiCommand(
597
691
  process.execPath,
@@ -638,71 +732,10 @@ function createInvestWikiRunnerFromOptions(options, env = process.env) {
638
732
  return createInvestWikiRunner(resolveInvestWikiConfig(options, env));
639
733
  }
640
734
 
641
- // src/local-config.ts
642
- import { chmod, mkdir, readFile, writeFile } from "fs/promises";
643
- import os from "os";
644
- import path2 from "path";
645
- import { z } from "zod";
646
- import { DOSSIERX_CONFIG_DIR } from "@xdsjs/dossierx-workspace";
647
- var DaemonLocalConfigSchema = z.object({
648
- machineId: z.string().uuid().optional(),
649
- machineKey: z.string().startsWith("dx_machine_"),
650
- serverUrl: z.string().min(1),
651
- supabaseUrl: z.string().min(1),
652
- supabaseAnonKey: z.string().min(1),
653
- workspacePath: z.string().min(1),
654
- investWikiMode: z.string().optional(),
655
- investWikiLocalRepo: z.string().optional(),
656
- investWikiCommand: z.string().optional(),
657
- codexCommand: z.string().optional(),
658
- codexModel: z.string().optional(),
659
- codexSandbox: z.string().optional(),
660
- localApiPort: z.number().int().positive().optional()
661
- });
662
- function expandHomePath(input) {
663
- if (input === "~") {
664
- return os.homedir();
665
- }
666
- if (input.startsWith("~/")) {
667
- return path2.join(os.homedir(), input.slice(2));
668
- }
669
- return input;
670
- }
671
- function getDaemonConfigDir(env = process.env) {
672
- return path2.resolve(
673
- expandHomePath(env.DOSSIERX_CONFIG_DIR ?? DOSSIERX_CONFIG_DIR)
674
- );
675
- }
676
- function daemonConfigPath(configDir = getDaemonConfigDir()) {
677
- return path2.join(configDir, "config.json");
678
- }
679
- async function readDaemonLocalConfig(configDir = getDaemonConfigDir()) {
680
- try {
681
- return DaemonLocalConfigSchema.parse(
682
- JSON.parse(await readFile(daemonConfigPath(configDir), "utf8"))
683
- );
684
- } catch (error) {
685
- if (error && typeof error === "object" && "code" in error && error.code === "ENOENT") {
686
- return null;
687
- }
688
- throw error;
689
- }
690
- }
691
- async function writeDaemonLocalConfig(config, configDir = getDaemonConfigDir()) {
692
- const parsed = DaemonLocalConfigSchema.parse(config);
693
- await mkdir(configDir, { recursive: true, mode: 448 });
694
- const target = daemonConfigPath(configDir);
695
- await writeFile(target, `${JSON.stringify(parsed, null, 2)}
696
- `, {
697
- mode: 384
698
- });
699
- await chmod(target, 384);
700
- }
701
-
702
735
  // src/local-api/server.ts
703
736
  import { createServer } from "http";
704
737
  import { readFile as readFile3, realpath, stat } from "fs/promises";
705
- import path4 from "path";
738
+ import path5 from "path";
706
739
  import { execa as execa3 } from "execa";
707
740
  import {
708
741
  resolveInsideWorkspace as resolveInsideWorkspace2,
@@ -712,7 +745,7 @@ import {
712
745
  // src/task-archive.ts
713
746
  import { randomUUID } from "crypto";
714
747
  import { appendFile, mkdir as mkdir2, readFile as readFile2 } from "fs/promises";
715
- import path3 from "path";
748
+ import path4 from "path";
716
749
  import { resolveInsideWorkspace } from "@xdsjs/dossierx-workspace";
717
750
  var TASK_ID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
718
751
  function assertTaskId(taskId) {
@@ -724,7 +757,7 @@ function taskDirectory(workspaceRoot, taskId) {
724
757
  assertTaskId(taskId);
725
758
  return resolveInsideWorkspace(
726
759
  workspaceRoot,
727
- path3.posix.join(".dossierx", "tasks", taskId)
760
+ path4.posix.join(".dossierx", "tasks", taskId)
728
761
  );
729
762
  }
730
763
  async function ensureTaskDirectory(workspaceRoot, taskId) {
@@ -757,7 +790,7 @@ function createTaskArchive(options) {
757
790
  created_at: now().toISOString()
758
791
  };
759
792
  await appendFile(
760
- path3.join(directory, "events.jsonl"),
793
+ path4.join(directory, "events.jsonl"),
761
794
  `${JSON.stringify(archivedEvent)}
762
795
  `,
763
796
  "utf8"
@@ -766,13 +799,13 @@ function createTaskArchive(options) {
766
799
  },
767
800
  async appendRawOutput(taskId, stream, chunk) {
768
801
  const directory = await ensureTaskDirectory(options.workspaceRoot, taskId);
769
- await appendFile(path3.join(directory, `${stream}.log`), chunk, "utf8");
802
+ await appendFile(path4.join(directory, `${stream}.log`), chunk, "utf8");
770
803
  },
771
804
  async listEvents(taskId) {
772
805
  const directory = taskDirectory(options.workspaceRoot, taskId);
773
806
  let content = "";
774
807
  try {
775
- content = await readFile2(path3.join(directory, "events.jsonl"), "utf8");
808
+ content = await readFile2(path4.join(directory, "events.jsonl"), "utf8");
776
809
  } catch (error) {
777
810
  if (error.code === "ENOENT") {
778
811
  return [];
@@ -786,7 +819,7 @@ function createTaskArchive(options) {
786
819
 
787
820
  // src/version.ts
788
821
  var DAEMON_PACKAGE_NAME = "@xdsjs/dossierx-daemon";
789
- var DAEMON_VERSION = "0.1.1";
822
+ var DAEMON_VERSION = "0.1.2";
790
823
 
791
824
  // src/local-api/server.ts
792
825
  var DEFAULT_MAX_FILE_BYTES = 5 * 1024 * 1024;
@@ -813,7 +846,7 @@ function requestUrl(request) {
813
846
  return new URL(request.url ?? "/", "http://127.0.0.1");
814
847
  }
815
848
  async function readWorkspaceFile(options) {
816
- const extension = path4.extname(options.relativePath).toLowerCase();
849
+ const extension = path5.extname(options.relativePath).toLowerCase();
817
850
  if (!ALLOWED_EXTENSIONS.has(extension)) {
818
851
  throw new Error("Only markdown, text, and json files can be previewed");
819
852
  }
@@ -865,7 +898,7 @@ function uniqueProbes(probes) {
865
898
  }
866
899
  async function resolveCommand(command) {
867
900
  const normalizedCommand = command.trim();
868
- if (path4.isAbsolute(normalizedCommand)) {
901
+ if (path5.isAbsolute(normalizedCommand)) {
869
902
  return realpath(normalizedCommand).catch(() => normalizedCommand);
870
903
  }
871
904
  const result = await execa3("which", [normalizedCommand], {
@@ -1213,6 +1246,7 @@ import {
1213
1246
  TaskEventInputSchema,
1214
1247
  TaskSchema
1215
1248
  } from "@xdsjs/dossierx-shared";
1249
+ import { GitMirrorError } from "@xdsjs/dossierx-git-mirror";
1216
1250
 
1217
1251
  // src/errors.ts
1218
1252
  function failTaskError(error, code = "UNKNOWN", step) {
@@ -1433,7 +1467,7 @@ async function runCodexTask(task, context) {
1433
1467
 
1434
1468
  // src/executors/investWiki.ts
1435
1469
  import { access as access2, mkdir as mkdir3, readFile as readFile4, stat as stat3, writeFile as writeFile2 } from "fs/promises";
1436
- import path5 from "path";
1470
+ import path6 from "path";
1437
1471
  import {
1438
1472
  buildCompanyManifest as buildCompanyManifest2,
1439
1473
  companyManifestPath as companyManifestPath2,
@@ -1611,7 +1645,7 @@ async function runInitCompanyVault(task, context) {
1611
1645
  const statusOutput = await runner.run(["dossier", "status"], { cwd: vaultRoot });
1612
1646
  await appendOutputEvent2(context, "Invest wiki dossier status completed", statusOutput);
1613
1647
  if (!await exists(markerPath)) {
1614
- await mkdir3(path5.dirname(markerPath), { recursive: true });
1648
+ await mkdir3(path6.dirname(markerPath), { recursive: true });
1615
1649
  await writeFile2(markerPath, "# Created by dossierx-daemon\n");
1616
1650
  }
1617
1651
  const manifest = await buildCompanyManifest2({
@@ -1699,7 +1733,7 @@ async function runInvestWikiTask(task, context) {
1699
1733
 
1700
1734
  // src/executors/mockWriteCompanyReport.ts
1701
1735
  import { mkdir as mkdir4, writeFile as writeFile3 } from "fs/promises";
1702
- import path6 from "path";
1736
+ import path7 from "path";
1703
1737
  import {
1704
1738
  buildCompanyManifest as buildCompanyManifest3,
1705
1739
  companyManifestPath as companyManifestPath3,
@@ -1739,7 +1773,7 @@ This is a placeholder right-price analysis for ${ticker}.
1739
1773
  `;
1740
1774
  async function writeWorkspaceFile(context, relativePath, content) {
1741
1775
  const absolutePath = resolveInsideWorkspace5(context.workspaceRoot, relativePath);
1742
- await mkdir4(path6.dirname(absolutePath), { recursive: true });
1776
+ await mkdir4(path7.dirname(absolutePath), { recursive: true });
1743
1777
  if (!context.dryRun) {
1744
1778
  await writeFile3(absolutePath, content);
1745
1779
  }
@@ -1873,7 +1907,7 @@ async function runTask(ctx, task, agent) {
1873
1907
  }
1874
1908
  await appendEvent(ctx, task.id, "Task claimed");
1875
1909
  try {
1876
- const result = await executor(task, {
1910
+ let result = await executor(task, {
1877
1911
  workspaceRoot: ctx.workspaceRoot,
1878
1912
  dryRun: ctx.dryRun,
1879
1913
  codex: codexRunnerForTask(ctx, agent),
@@ -1885,6 +1919,31 @@ async function runTask(ctx, task, agent) {
1885
1919
  await appendLocalRawOutput(ctx, task.id, stream, chunk);
1886
1920
  }
1887
1921
  });
1922
+ if (ctx.gitMirror && result.manifest) {
1923
+ const gitSnapshot = await ctx.gitMirror.publishCompanySnapshot({
1924
+ workspaceRoot: ctx.workspaceRoot,
1925
+ company: {
1926
+ ticker: result.manifest.ticker,
1927
+ market: result.manifest.market
1928
+ },
1929
+ taskId: task.id
1930
+ });
1931
+ await appendLocalEvent(ctx, task.id, {
1932
+ level: "info",
1933
+ message: gitSnapshot.status === "published" ? "Git mirror snapshot published" : "Git mirror snapshot unchanged",
1934
+ data: {
1935
+ commitSha: gitSnapshot.commitSha,
1936
+ treeHash: gitSnapshot.treeHash,
1937
+ branch: gitSnapshot.branch,
1938
+ pushed: gitSnapshot.pushed
1939
+ }
1940
+ });
1941
+ result = {
1942
+ ...result,
1943
+ commitSha: gitSnapshot.commitSha ?? result.commitSha,
1944
+ gitSnapshot
1945
+ };
1946
+ }
1888
1947
  await appendEvent(ctx, task.id, "Task completed");
1889
1948
  await ctx.api.completeTask(task.id, { result });
1890
1949
  } catch (error) {
@@ -1920,6 +1979,22 @@ async function runTask(ctx, task, agent) {
1920
1979
  });
1921
1980
  return;
1922
1981
  }
1982
+ if (error instanceof GitMirrorError) {
1983
+ await appendLocalEvent(ctx, task.id, {
1984
+ level: "error",
1985
+ message: error.message,
1986
+ data: { code: error.code, step: error.step }
1987
+ });
1988
+ await ctx.api.failTask(task.id, {
1989
+ error: {
1990
+ code: error.code,
1991
+ message: error.message,
1992
+ step: error.step,
1993
+ recoverable: false
1994
+ }
1995
+ });
1996
+ return;
1997
+ }
1923
1998
  await appendLocalEvent(ctx, task.id, {
1924
1999
  level: "error",
1925
2000
  message: error instanceof Error ? error.message : "Task failed",
@@ -2103,7 +2178,7 @@ async function subscribeToTaskAvailable(options, onEvent, onInvalidEvent) {
2103
2178
  // src/service.ts
2104
2179
  import { mkdir as mkdir5, unlink, writeFile as writeFile4 } from "fs/promises";
2105
2180
  import os2 from "os";
2106
- import path7 from "path";
2181
+ import path8 from "path";
2107
2182
  var LAUNCH_AGENT_LABEL = "com.xdsjs.dossierx-daemon";
2108
2183
  function xmlEscape(value) {
2109
2184
  return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&apos;");
@@ -2112,10 +2187,10 @@ function shellQuote(value) {
2112
2187
  return `'${value.replaceAll("'", "'\\''")}'`;
2113
2188
  }
2114
2189
  function launchAgentsDir() {
2115
- return path7.join(os2.homedir(), "Library", "LaunchAgents");
2190
+ return path8.join(os2.homedir(), "Library", "LaunchAgents");
2116
2191
  }
2117
2192
  function launchAgentPlistPath(label = LAUNCH_AGENT_LABEL, dir = launchAgentsDir()) {
2118
- return path7.join(dir, `${label}.plist`);
2193
+ return path8.join(dir, `${label}.plist`);
2119
2194
  }
2120
2195
  function resolveDaemonProgramArguments(input) {
2121
2196
  if (input.daemonCommand?.trim()) {
@@ -2126,14 +2201,14 @@ function resolveDaemonProgramArguments(input) {
2126
2201
  if (!entry) {
2127
2202
  throw new Error("Unable to resolve daemon entrypoint for LaunchAgent");
2128
2203
  }
2129
- if (path7.extname(entry) === ".ts") {
2204
+ if (path8.extname(entry) === ".ts") {
2130
2205
  throw new Error(
2131
2206
  "LaunchAgent cannot run a TypeScript daemon entrypoint directly; pass --daemon-command when installing from source"
2132
2207
  );
2133
2208
  }
2134
2209
  return [
2135
2210
  input.execPath ?? process.execPath,
2136
- path7.resolve(entry),
2211
+ path8.resolve(entry),
2137
2212
  "--log-level",
2138
2213
  input.logLevel ?? "info"
2139
2214
  ];
@@ -2180,6 +2255,10 @@ function runtimeOptionsFrom(input) {
2180
2255
  codexCommand: input.codexCommand,
2181
2256
  codexModel: input.codexModel,
2182
2257
  codexSandbox: input.codexSandbox,
2258
+ gitMirrorRoot: input.gitMirrorRoot,
2259
+ gitMirrorRemote: input.gitMirrorRemote,
2260
+ gitMirrorBranch: input.gitMirrorBranch,
2261
+ gitCommand: input.gitCommand,
2183
2262
  localApiPort: input.localApiPort
2184
2263
  };
2185
2264
  }
@@ -2188,7 +2267,7 @@ async function installLaunchAgent(options = {}) {
2188
2267
  throw new Error("DossierX LaunchAgent service is only supported on macOS");
2189
2268
  }
2190
2269
  const label = options.label ?? LAUNCH_AGENT_LABEL;
2191
- const configDir = path7.resolve(
2270
+ const configDir = path8.resolve(
2192
2271
  expandHomePath(options.configDir ?? getDaemonConfigDir())
2193
2272
  );
2194
2273
  const config = await readDaemonLocalConfig(configDir);
@@ -2208,8 +2287,8 @@ async function installLaunchAgent(options = {}) {
2208
2287
  );
2209
2288
  }
2210
2289
  const plistPath = launchAgentPlistPath(label, options.launchAgentsDir);
2211
- const stdoutPath = path7.join(configDir, "daemon.out.log");
2212
- const stderrPath = path7.join(configDir, "daemon.err.log");
2290
+ const stdoutPath = path8.join(configDir, "daemon.out.log");
2291
+ const stderrPath = path8.join(configDir, "daemon.err.log");
2213
2292
  const uid = typeof process.getuid === "function" ? process.getuid() : "<uid>";
2214
2293
  const programArguments = resolveDaemonProgramArguments({
2215
2294
  daemonCommand: options.daemonCommand,
@@ -2217,7 +2296,7 @@ async function installLaunchAgent(options = {}) {
2217
2296
  argv: options.argv,
2218
2297
  execPath: options.execPath
2219
2298
  });
2220
- await mkdir5(path7.dirname(plistPath), { recursive: true });
2299
+ await mkdir5(path8.dirname(plistPath), { recursive: true });
2221
2300
  await mkdir5(configDir, { recursive: true, mode: 448 });
2222
2301
  await writeFile4(
2223
2302
  plistPath,
@@ -2259,7 +2338,14 @@ async function uninstallLaunchAgent(options = {}) {
2259
2338
  }
2260
2339
 
2261
2340
  // src/cli.ts
2262
- async function assertWorkspaceExists(workspace) {
2341
+ async function ensureWorkspaceDirectory(workspace) {
2342
+ await mkdir6(workspace, { recursive: true }).catch(async (error) => {
2343
+ const stats2 = await stat4(workspace).catch(() => null);
2344
+ if (!stats2?.isDirectory()) {
2345
+ throw new Error("Workspace path is not a directory");
2346
+ }
2347
+ throw error;
2348
+ });
2263
2349
  const stats = await stat4(workspace);
2264
2350
  if (!stats.isDirectory()) {
2265
2351
  throw new Error("Workspace path is not a directory");
@@ -2302,11 +2388,11 @@ function buildProgram() {
2302
2388
  ).option("--codex-command <command>", "installed Codex CLI command").option("--codex-model <model>", "Codex model override for exec mode").option(
2303
2389
  "--codex-sandbox <mode>",
2304
2390
  "Codex sandbox mode: workspace-write or danger-full-access"
2305
- ).option("--local-api-port <port>", "local workspace preview API port").option("--no-local-api", "disable local workspace preview API").action(async (options) => {
2391
+ ).option("--git-mirror-root <path>", "local Git mirror repository path").option("--git-mirror-remote <url>", "remote Git mirror repository URL").option("--git-mirror-branch <branch>", "Git mirror branch name").option("--git-command <command>", "installed Git command").option("--local-api-port <port>", "local workspace preview API port").option("--no-local-api", "disable local workspace preview API").action(async (options) => {
2306
2392
  await runDaemon(options);
2307
2393
  });
2308
2394
  const service = program.command("service").description("Manage the macOS LaunchAgent for dossierx-daemon");
2309
- service.command("install").description("Write a macOS LaunchAgent plist for persistent daemon runs").option("--label <label>", "LaunchAgent label").option("--daemon-command <command>", "custom command used by launchd").option("--log-level <level>", "daemon log level", "info").option("--invest-wiki-mode <mode>", "invest wiki runner mode").option("--invest-wiki-local-repo <path>", "local llm-wiki-invest repo path").option("--invest-wiki-command <command>", "installed llm-wiki-invest command").option("--codex-command <command>", "installed Codex CLI command").option("--codex-model <model>", "Codex model override for exec mode").option("--codex-sandbox <mode>", "Codex sandbox mode").option("--local-api-port <port>", "local workspace preview API port").action(async (options) => {
2395
+ service.command("install").description("Write a macOS LaunchAgent plist for persistent daemon runs").option("--label <label>", "LaunchAgent label").option("--daemon-command <command>", "custom command used by launchd").option("--log-level <level>", "daemon log level", "info").option("--invest-wiki-mode <mode>", "invest wiki runner mode").option("--invest-wiki-local-repo <path>", "local llm-wiki-invest repo path").option("--invest-wiki-command <command>", "installed llm-wiki-invest command").option("--codex-command <command>", "installed Codex CLI command").option("--codex-model <model>", "Codex model override for exec mode").option("--codex-sandbox <mode>", "Codex sandbox mode").option("--git-mirror-root <path>", "local Git mirror repository path").option("--git-mirror-remote <url>", "remote Git mirror repository URL").option("--git-mirror-branch <branch>", "Git mirror branch name").option("--git-command <command>", "installed Git command").option("--local-api-port <port>", "local workspace preview API port").action(async (options) => {
2310
2396
  const result = await installLaunchAgent({
2311
2397
  ...options,
2312
2398
  localApiPort: parsePort(options.localApiPort)
@@ -2340,6 +2426,10 @@ async function runDaemon(options) {
2340
2426
  codexCommand: options.codexCommand ?? localConfig?.codexCommand,
2341
2427
  codexModel: options.codexModel ?? localConfig?.codexModel,
2342
2428
  codexSandbox: options.codexSandbox ?? localConfig?.codexSandbox,
2429
+ gitMirrorRoot: options.gitMirrorRoot ?? localConfig?.gitMirrorRoot,
2430
+ gitMirrorRemote: options.gitMirrorRemote ?? localConfig?.gitMirrorRemote,
2431
+ gitMirrorBranch: options.gitMirrorBranch ?? localConfig?.gitMirrorBranch,
2432
+ gitCommand: options.gitCommand ?? localConfig?.gitCommand,
2343
2433
  localApiPort: parsePort(options.localApiPort) ?? localConfig?.localApiPort ?? parsePort(process.env.DOSSIERX_LOCAL_API_PORT) ?? DOSSIERX_DEFAULT_LOCAL_API_PORT
2344
2434
  };
2345
2435
  if (!serverUrl || !supabaseUrl || !supabaseAnonKey || !machineKey) {
@@ -2347,11 +2437,12 @@ async function runDaemon(options) {
2347
2437
  "Missing daemon connection config. Run the generated daemon command from DossierX first."
2348
2438
  );
2349
2439
  }
2350
- const workspaceRoot = path8.resolve(expandHomePath(workspacePath3));
2351
- await assertWorkspaceExists(workspaceRoot);
2440
+ const workspaceRoot = path9.resolve(expandHomePath(workspacePath3));
2441
+ await ensureWorkspaceDirectory(workspaceRoot);
2352
2442
  const capabilities = await detectCapabilities();
2353
2443
  const investWiki = createInvestWikiRunnerFromOptions(runtimeOptions);
2354
2444
  const codex = createCodexRunnerFromOptions(runtimeOptions);
2445
+ const gitMirror = createGitMirrorFromOptions(runtimeOptions);
2355
2446
  const taskArchive = createTaskArchive({ workspaceRoot });
2356
2447
  const api = new ApiClient({
2357
2448
  serverUrl,
@@ -2377,6 +2468,10 @@ async function runDaemon(options) {
2377
2468
  codexCommand: runtimeOptions.codexCommand,
2378
2469
  codexModel: runtimeOptions.codexModel,
2379
2470
  codexSandbox: runtimeOptions.codexSandbox,
2471
+ gitMirrorRoot: runtimeOptions.gitMirrorRoot,
2472
+ gitMirrorRemote: runtimeOptions.gitMirrorRemote,
2473
+ gitMirrorBranch: runtimeOptions.gitMirrorBranch,
2474
+ gitCommand: runtimeOptions.gitCommand,
2380
2475
  localApiPort: runtimeOptions.localApiPort
2381
2476
  });
2382
2477
  const state = { running: false, pending: false };
@@ -2390,6 +2485,7 @@ async function runDaemon(options) {
2390
2485
  codex,
2391
2486
  codexOptions: runtimeOptions,
2392
2487
  investWiki,
2488
+ gitMirror,
2393
2489
  taskArchive
2394
2490
  };
2395
2491
  async function heartbeat(status) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xdsjs/dossierx-daemon",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -28,8 +28,9 @@
28
28
  "execa": "^9.0.0",
29
29
  "pino": "^10.0.0",
30
30
  "zod": "^4.0.0",
31
- "@xdsjs/dossierx-shared": "^0.1.1",
32
- "@xdsjs/dossierx-workspace": "^0.1.0"
31
+ "@xdsjs/dossierx-git-mirror": "^0.1.0",
32
+ "@xdsjs/dossierx-workspace": "^0.1.0",
33
+ "@xdsjs/dossierx-shared": "^0.1.2"
33
34
  },
34
35
  "devDependencies": {
35
36
  "@types/node": "^24.0.0",