@hasna/workflows 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.
package/README.md CHANGED
@@ -102,7 +102,7 @@ console.log(svc.health());
102
102
  |---|---|---|
103
103
  | `HASNA_WORKFLOWS_PORT` / `WORKFLOWS_PORT` | serve port | 8790 |
104
104
  | `HASNA_WORKFLOWS_HOST` / `WORKFLOWS_HOST` | serve bind host | 127.0.0.1 |
105
- | `HASNA_WORKFLOWS_DATA_DIR` / `WORKFLOWS_DATA_DIR` | data directory | `~/.hasna/workflows` |
105
+ | `HASNA_WORKFLOWS_DATA_DIR` / `WORKFLOWS_DATA_DIR` | data directory | `~/.hasna/workflows` (the @hasna/paths XDG data home `~/.local/share/hasna/workflows` is adopted once `HASNA_DATA_HOME` is set or the store is migrated there) |
106
106
 
107
107
  The server data backend is sqlite (default) or postgresql when
108
108
  `HASNA_WORKFLOWS_DATABASE_URL` is set.
@@ -1411,9 +1411,9 @@ class WorkflowsDaemon {
1411
1411
  }
1412
1412
  const output = { ok: result.ok, exitCode: result.exitCode, output: result.output, durationMs: result.durationMs };
1413
1413
  if (!result.ok) {
1414
- const retriesLeft = (node.maxRetries ?? 0) + 1 - nodeRow.attempts;
1415
- if (retriesLeft > 0) {
1416
- this.store.bumpAttemptsNode(nodeRow.id);
1414
+ const shouldRetry = nodeRow.attempts < (node.maxRetries ?? 0);
1415
+ this.store.bumpAttemptsNode(nodeRow.id);
1416
+ if (shouldRetry) {
1417
1417
  this.store.setRunNodeStatus(nodeRow.id, "pending", { error: result.error ?? `exit ${result.exitCode}` });
1418
1418
  return { kind: "advanced", nodeId };
1419
1419
  }
package/dist/cli/index.js CHANGED
@@ -443,16 +443,13 @@ var init_lanes = __esm(() => {
443
443
 
444
444
  // src/cli/index.ts
445
445
  import { Command } from "commander";
446
- import { existsSync as existsSync2, mkdirSync as mkdirSync3, readFileSync as readFileSync2, writeFileSync as writeFileSync2, rmSync } from "fs";
446
+ import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync2, writeFileSync as writeFileSync2, rmSync } from "fs";
447
447
  import { hostname } from "os";
448
448
  import { join as join4 } from "path";
449
-
450
- // src/service.ts
451
- import { join as join3 } from "path";
452
449
  // package.json
453
450
  var package_default = {
454
451
  name: "@hasna/workflows",
455
- version: "0.1.1",
452
+ version: "0.1.3",
456
453
  description: "Universal graph workflow app \u2014 graph language (start/step/decision/while/end), three-table store, session WAL, daemon with claims/leases, four lane adapters (claude/codex/cursor/grok), and a full CLI on four surfaces",
457
454
  type: "module",
458
455
  main: "./dist/index.js",
@@ -517,15 +514,15 @@ var package_default = {
517
514
  url: "https://github.com/hasna/apps/issues"
518
515
  },
519
516
  engines: {
520
- bun: ">=1.0.0",
521
- node: ">=20.0.0"
517
+ bun: ">=1.0.0"
522
518
  },
523
519
  author: "Hasna",
524
520
  license: "Apache-2.0",
525
521
  dependencies: {
526
- "@anthropic-ai/claude-agent-sdk": "^0.3.234",
522
+ "@anthropic-ai/claude-agent-sdk": "0.3.234",
527
523
  "@cursor/sdk": "^1.0.28",
528
- "@openai/codex-sdk": "^0.147.0",
524
+ "@hasna/paths": "0.1.0",
525
+ "@openai/codex-sdk": "0.147.0",
529
526
  commander: "^13.1.0"
530
527
  },
531
528
  devDependencies: {
@@ -1536,9 +1533,9 @@ class WorkflowsDaemon {
1536
1533
  }
1537
1534
  const output = { ok: result.ok, exitCode: result.exitCode, output: result.output, durationMs: result.durationMs };
1538
1535
  if (!result.ok) {
1539
- const retriesLeft = (node.maxRetries ?? 0) + 1 - nodeRow.attempts;
1540
- if (retriesLeft > 0) {
1541
- this.store.bumpAttemptsNode(nodeRow.id);
1536
+ const shouldRetry = nodeRow.attempts < (node.maxRetries ?? 0);
1537
+ this.store.bumpAttemptsNode(nodeRow.id);
1538
+ if (shouldRetry) {
1542
1539
  this.store.setRunNodeStatus(nodeRow.id, "pending", { error: result.error ?? `exit ${result.exitCode}` });
1543
1540
  return { kind: "advanced", nodeId };
1544
1541
  }
@@ -1670,17 +1667,44 @@ async function runGraphToCompletion(store, wal, graph, context, options = {}) {
1670
1667
  return store.getRun(run.id);
1671
1668
  }
1672
1669
 
1670
+ // src/paths.ts
1671
+ import { existsSync } from "fs";
1672
+ import { homedir } from "os";
1673
+ import { join } from "path";
1674
+ import { dataDir } from "@hasna/paths";
1675
+ var APP = "workflows";
1676
+ var STORE_DB_FILE = "workflows.db";
1677
+ function getHomeDir(env = process.env) {
1678
+ return env["HOME"] || env["USERPROFILE"] || homedir();
1679
+ }
1680
+ function getLegacyDataDir(env = process.env) {
1681
+ return join(getHomeDir(env), ".hasna", APP);
1682
+ }
1683
+ function getResolverDataDir(env = process.env) {
1684
+ return dataDir({ app: APP, home: getHomeDir(env), env });
1685
+ }
1686
+ function adoptResolverDataDir(resolved, env = process.env) {
1687
+ const dataOverride = env.HASNA_DATA_HOME;
1688
+ if (typeof dataOverride === "string" && dataOverride.trim().length > 0)
1689
+ return true;
1690
+ return existsSync(join(resolved, STORE_DB_FILE));
1691
+ }
1692
+ function getEffectiveDataDir(env = process.env) {
1693
+ const resolved = getResolverDataDir(env);
1694
+ return adoptResolverDataDir(resolved, env) ? resolved : getLegacyDataDir(env);
1695
+ }
1696
+
1673
1697
  // src/store.ts
1674
1698
  import { Database } from "bun:sqlite";
1675
1699
  import { mkdirSync } from "fs";
1676
- import { join } from "path";
1700
+ import { join as join2 } from "path";
1677
1701
  import { randomUUID } from "crypto";
1678
1702
  function nowIso2() {
1679
1703
  return new Date().toISOString();
1680
1704
  }
1681
- function openStore(dataDir) {
1682
- mkdirSync(dataDir, { recursive: true });
1683
- const db = new Database(join(dataDir, "workflows.db"));
1705
+ function openStore(dataDir2) {
1706
+ mkdirSync(dataDir2, { recursive: true });
1707
+ const db = new Database(join2(dataDir2, "workflows.db"));
1684
1708
  db.exec("PRAGMA journal_mode = WAL");
1685
1709
  db.exec("PRAGMA foreign_keys = ON");
1686
1710
  db.exec(`
@@ -1858,8 +1882,8 @@ function openStore(dataDir) {
1858
1882
 
1859
1883
  // src/wal.ts
1860
1884
  import { createHash as createHash3 } from "crypto";
1861
- import { appendFileSync, existsSync, mkdirSync as mkdirSync2, readFileSync, renameSync, truncateSync, writeFileSync } from "fs";
1862
- import { join as join2 } from "path";
1885
+ import { appendFileSync, existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync, renameSync, truncateSync, writeFileSync } from "fs";
1886
+ import { join as join3 } from "path";
1863
1887
  var WAL_FILE_NAME = "session.wal";
1864
1888
  var SESSIONS_DIR_NAME = "sessions";
1865
1889
  var MAX_WAL_BYTES = 256 * 1024 * 1024;
@@ -1874,11 +1898,11 @@ class SessionWAL {
1874
1898
  constructor(filePath) {
1875
1899
  this.filePath = filePath;
1876
1900
  }
1877
- static open(dataDir) {
1878
- mkdirSync2(join2(dataDir, SESSIONS_DIR_NAME), { recursive: true });
1879
- const canonical = join2(dataDir, SESSIONS_DIR_NAME, WAL_FILE_NAME);
1880
- const legacy = join2(dataDir, WAL_FILE_NAME);
1881
- if (existsSync(legacy) && !existsSync(canonical)) {
1901
+ static open(dataDir2) {
1902
+ mkdirSync2(join3(dataDir2, SESSIONS_DIR_NAME), { recursive: true });
1903
+ const canonical = join3(dataDir2, SESSIONS_DIR_NAME, WAL_FILE_NAME);
1904
+ const legacy = join3(dataDir2, WAL_FILE_NAME);
1905
+ if (existsSync2(legacy) && !existsSync2(canonical)) {
1882
1906
  try {
1883
1907
  renameSync(legacy, canonical);
1884
1908
  } catch {
@@ -2009,11 +2033,10 @@ function envInt(names, fallback) {
2009
2033
  return Number.isFinite(parsed) ? parsed : fallback;
2010
2034
  }
2011
2035
  function resolveWorkflowsConfig(overrides = {}) {
2012
- const home = process.env.HOME ?? process.env.USERPROFILE ?? ".";
2013
2036
  return {
2014
2037
  port: overrides.port ?? envInt(["HASNA_WORKFLOWS_PORT", "WORKFLOWS_PORT"], DEFAULT_PORT),
2015
2038
  host: overrides.host ?? envString(["HASNA_WORKFLOWS_HOST", "WORKFLOWS_HOST"]) ?? DEFAULT_HOST,
2016
- dataDir: overrides.dataDir ?? envString(["HASNA_WORKFLOWS_DATA_DIR", "WORKFLOWS_DATA_DIR"]) ?? join3(home, ".hasna", "workflows"),
2039
+ dataDir: overrides.dataDir ?? envString(["HASNA_WORKFLOWS_DATA_DIR", "WORKFLOWS_DATA_DIR"]) ?? getEffectiveDataDir(),
2017
2040
  apiUrl: overrides.apiUrl ?? envString(["WORKFLOWS_API_URL"]),
2018
2041
  apiKey: overrides.apiKey ?? envString(["WORKFLOWS_API_KEY"])
2019
2042
  };
@@ -2404,7 +2427,7 @@ program.command("init").description("Create the store layout (workflows/, sessio
2404
2427
  const store = openStore(config.dataDir);
2405
2428
  store.close();
2406
2429
  const target = file ?? join4(config.dataDir, "workflows", "demo.json");
2407
- if (existsSync2(target)) {
2430
+ if (existsSync3(target)) {
2408
2431
  console.error(`refusing to overwrite existing file ${target}`);
2409
2432
  process.exit(1);
2410
2433
  }
@@ -2691,16 +2714,16 @@ program.command("machines").description("List observed machines or report this m
2691
2714
  case "status": {
2692
2715
  const replay = wal.replay();
2693
2716
  const pidFile = join4(config.dataDir, "daemon.pid");
2694
- const pid = existsSync2(pidFile) ? readFileSync2(pidFile, "utf8").trim() : null;
2717
+ const pid = existsSync3(pidFile) ? readFileSync2(pidFile, "utf8").trim() : null;
2695
2718
  const running = pid !== null && processAlive(Number(pid));
2696
2719
  const report = {
2697
2720
  hostname: hostname(),
2698
2721
  pid: process.pid,
2699
2722
  dataDir: config.dataDir,
2700
2723
  layout: {
2701
- workflows: existsSync2(join4(config.dataDir, "workflows")),
2702
- sessions: existsSync2(join4(config.dataDir, SESSIONS_DIR_NAME)),
2703
- db: existsSync2(join4(config.dataDir, "workflows.db"))
2724
+ workflows: existsSync3(join4(config.dataDir, "workflows")),
2725
+ sessions: existsSync3(join4(config.dataDir, SESSIONS_DIR_NAME)),
2726
+ db: existsSync3(join4(config.dataDir, "workflows.db"))
2704
2727
  },
2705
2728
  store: {
2706
2729
  tables: store.listTables(),
@@ -2813,7 +2836,7 @@ program.command("daemon").description("Run or inspect the daemon").argument("<ac
2813
2836
  }
2814
2837
  }
2815
2838
  case "status": {
2816
- if (!existsSync2(statusFile)) {
2839
+ if (!existsSync3(statusFile)) {
2817
2840
  if (opts.json) {
2818
2841
  console.log(JSON.stringify({ running: false, lastReap: null }));
2819
2842
  } else {
@@ -2822,7 +2845,7 @@ program.command("daemon").description("Run or inspect the daemon").argument("<ac
2822
2845
  return;
2823
2846
  }
2824
2847
  const lastReap = JSON.parse(readFileSync2(statusFile, "utf8"));
2825
- const pid = existsSync2(pidFile) ? readFileSync2(pidFile, "utf8").trim() : null;
2848
+ const pid = existsSync3(pidFile) ? readFileSync2(pidFile, "utf8").trim() : null;
2826
2849
  const running = pid !== null && processAlive(Number(pid));
2827
2850
  if (opts.json) {
2828
2851
  console.log(JSON.stringify({ running, pid, lastReap }, null, 2));
@@ -2832,7 +2855,7 @@ program.command("daemon").description("Run or inspect the daemon").argument("<ac
2832
2855
  return;
2833
2856
  }
2834
2857
  case "stop": {
2835
- if (!existsSync2(pidFile)) {
2858
+ if (!existsSync3(pidFile)) {
2836
2859
  console.error("no daemon pid file \u2014 nothing to stop");
2837
2860
  process.exit(1);
2838
2861
  }
package/dist/index.js CHANGED
@@ -439,13 +439,10 @@ var init_lanes = __esm(() => {
439
439
  ["grok", createGrokAdapter()]
440
440
  ]);
441
441
  });
442
-
443
- // src/service.ts
444
- import { join as join3 } from "path";
445
442
  // package.json
446
443
  var package_default = {
447
444
  name: "@hasna/workflows",
448
- version: "0.1.1",
445
+ version: "0.1.3",
449
446
  description: "Universal graph workflow app \u2014 graph language (start/step/decision/while/end), three-table store, session WAL, daemon with claims/leases, four lane adapters (claude/codex/cursor/grok), and a full CLI on four surfaces",
450
447
  type: "module",
451
448
  main: "./dist/index.js",
@@ -510,15 +507,15 @@ var package_default = {
510
507
  url: "https://github.com/hasna/apps/issues"
511
508
  },
512
509
  engines: {
513
- bun: ">=1.0.0",
514
- node: ">=20.0.0"
510
+ bun: ">=1.0.0"
515
511
  },
516
512
  author: "Hasna",
517
513
  license: "Apache-2.0",
518
514
  dependencies: {
519
- "@anthropic-ai/claude-agent-sdk": "^0.3.234",
515
+ "@anthropic-ai/claude-agent-sdk": "0.3.234",
520
516
  "@cursor/sdk": "^1.0.28",
521
- "@openai/codex-sdk": "^0.147.0",
517
+ "@hasna/paths": "0.1.0",
518
+ "@openai/codex-sdk": "0.147.0",
522
519
  commander: "^13.1.0"
523
520
  },
524
521
  devDependencies: {
@@ -1501,9 +1498,9 @@ class WorkflowsDaemon {
1501
1498
  }
1502
1499
  const output = { ok: result.ok, exitCode: result.exitCode, output: result.output, durationMs: result.durationMs };
1503
1500
  if (!result.ok) {
1504
- const retriesLeft = (node.maxRetries ?? 0) + 1 - nodeRow.attempts;
1505
- if (retriesLeft > 0) {
1506
- this.store.bumpAttemptsNode(nodeRow.id);
1501
+ const shouldRetry = nodeRow.attempts < (node.maxRetries ?? 0);
1502
+ this.store.bumpAttemptsNode(nodeRow.id);
1503
+ if (shouldRetry) {
1507
1504
  this.store.setRunNodeStatus(nodeRow.id, "pending", { error: result.error ?? `exit ${result.exitCode}` });
1508
1505
  return { kind: "advanced", nodeId };
1509
1506
  }
@@ -1635,17 +1632,44 @@ async function runGraphToCompletion(store, wal, graph, context, options = {}) {
1635
1632
  return store.getRun(run.id);
1636
1633
  }
1637
1634
 
1635
+ // src/paths.ts
1636
+ import { existsSync } from "fs";
1637
+ import { homedir } from "os";
1638
+ import { join } from "path";
1639
+ import { dataDir } from "@hasna/paths";
1640
+ var APP = "workflows";
1641
+ var STORE_DB_FILE = "workflows.db";
1642
+ function getHomeDir(env = process.env) {
1643
+ return env["HOME"] || env["USERPROFILE"] || homedir();
1644
+ }
1645
+ function getLegacyDataDir(env = process.env) {
1646
+ return join(getHomeDir(env), ".hasna", APP);
1647
+ }
1648
+ function getResolverDataDir(env = process.env) {
1649
+ return dataDir({ app: APP, home: getHomeDir(env), env });
1650
+ }
1651
+ function adoptResolverDataDir(resolved, env = process.env) {
1652
+ const dataOverride = env.HASNA_DATA_HOME;
1653
+ if (typeof dataOverride === "string" && dataOverride.trim().length > 0)
1654
+ return true;
1655
+ return existsSync(join(resolved, STORE_DB_FILE));
1656
+ }
1657
+ function getEffectiveDataDir(env = process.env) {
1658
+ const resolved = getResolverDataDir(env);
1659
+ return adoptResolverDataDir(resolved, env) ? resolved : getLegacyDataDir(env);
1660
+ }
1661
+
1638
1662
  // src/store.ts
1639
1663
  import { Database } from "bun:sqlite";
1640
1664
  import { mkdirSync } from "fs";
1641
- import { join } from "path";
1665
+ import { join as join2 } from "path";
1642
1666
  import { randomUUID } from "crypto";
1643
1667
  function nowIso2() {
1644
1668
  return new Date().toISOString();
1645
1669
  }
1646
- function openStore(dataDir) {
1647
- mkdirSync(dataDir, { recursive: true });
1648
- const db = new Database(join(dataDir, "workflows.db"));
1670
+ function openStore(dataDir2) {
1671
+ mkdirSync(dataDir2, { recursive: true });
1672
+ const db = new Database(join2(dataDir2, "workflows.db"));
1649
1673
  db.exec("PRAGMA journal_mode = WAL");
1650
1674
  db.exec("PRAGMA foreign_keys = ON");
1651
1675
  db.exec(`
@@ -1823,8 +1847,8 @@ function openStore(dataDir) {
1823
1847
 
1824
1848
  // src/wal.ts
1825
1849
  import { createHash as createHash3 } from "crypto";
1826
- import { appendFileSync, existsSync, mkdirSync as mkdirSync2, readFileSync, renameSync, truncateSync, writeFileSync } from "fs";
1827
- import { join as join2 } from "path";
1850
+ import { appendFileSync, existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync, renameSync, truncateSync, writeFileSync } from "fs";
1851
+ import { join as join3 } from "path";
1828
1852
  var WAL_FILE_NAME = "session.wal";
1829
1853
  var SESSIONS_DIR_NAME = "sessions";
1830
1854
  var MAX_WAL_BYTES = 256 * 1024 * 1024;
@@ -1839,11 +1863,11 @@ class SessionWAL {
1839
1863
  constructor(filePath) {
1840
1864
  this.filePath = filePath;
1841
1865
  }
1842
- static open(dataDir) {
1843
- mkdirSync2(join2(dataDir, SESSIONS_DIR_NAME), { recursive: true });
1844
- const canonical = join2(dataDir, SESSIONS_DIR_NAME, WAL_FILE_NAME);
1845
- const legacy = join2(dataDir, WAL_FILE_NAME);
1846
- if (existsSync(legacy) && !existsSync(canonical)) {
1866
+ static open(dataDir2) {
1867
+ mkdirSync2(join3(dataDir2, SESSIONS_DIR_NAME), { recursive: true });
1868
+ const canonical = join3(dataDir2, SESSIONS_DIR_NAME, WAL_FILE_NAME);
1869
+ const legacy = join3(dataDir2, WAL_FILE_NAME);
1870
+ if (existsSync2(legacy) && !existsSync2(canonical)) {
1847
1871
  try {
1848
1872
  renameSync(legacy, canonical);
1849
1873
  } catch {
@@ -1974,11 +1998,10 @@ function envInt(names, fallback) {
1974
1998
  return Number.isFinite(parsed) ? parsed : fallback;
1975
1999
  }
1976
2000
  function resolveWorkflowsConfig(overrides = {}) {
1977
- const home = process.env.HOME ?? process.env.USERPROFILE ?? ".";
1978
2001
  return {
1979
2002
  port: overrides.port ?? envInt(["HASNA_WORKFLOWS_PORT", "WORKFLOWS_PORT"], DEFAULT_PORT),
1980
2003
  host: overrides.host ?? envString(["HASNA_WORKFLOWS_HOST", "WORKFLOWS_HOST"]) ?? DEFAULT_HOST,
1981
- dataDir: overrides.dataDir ?? envString(["HASNA_WORKFLOWS_DATA_DIR", "WORKFLOWS_DATA_DIR"]) ?? join3(home, ".hasna", "workflows"),
2004
+ dataDir: overrides.dataDir ?? envString(["HASNA_WORKFLOWS_DATA_DIR", "WORKFLOWS_DATA_DIR"]) ?? getEffectiveDataDir(),
1982
2005
  apiUrl: overrides.apiUrl ?? envString(["WORKFLOWS_API_URL"]),
1983
2006
  apiKey: overrides.apiKey ?? envString(["WORKFLOWS_API_KEY"])
1984
2007
  };
package/dist/mcp/index.js CHANGED
@@ -1415,9 +1415,9 @@ class WorkflowsDaemon {
1415
1415
  }
1416
1416
  const output = { ok: result.ok, exitCode: result.exitCode, output: result.output, durationMs: result.durationMs };
1417
1417
  if (!result.ok) {
1418
- const retriesLeft = (node.maxRetries ?? 0) + 1 - nodeRow.attempts;
1419
- if (retriesLeft > 0) {
1420
- this.store.bumpAttemptsNode(nodeRow.id);
1418
+ const shouldRetry = nodeRow.attempts < (node.maxRetries ?? 0);
1419
+ this.store.bumpAttemptsNode(nodeRow.id);
1420
+ if (shouldRetry) {
1421
1421
  this.store.setRunNodeStatus(nodeRow.id, "pending", { error: result.error ?? `exit ${result.exitCode}` });
1422
1422
  return { kind: "advanced", nodeId };
1423
1423
  }
@@ -1974,13 +1974,10 @@ async function callWorkflowTool(service, name, args) {
1974
1974
  throw new Error(`Unknown tool: ${name}`);
1975
1975
  }
1976
1976
  }
1977
-
1978
- // src/service.ts
1979
- import { join as join3 } from "path";
1980
1977
  // package.json
1981
1978
  var package_default = {
1982
1979
  name: "@hasna/workflows",
1983
- version: "0.1.1",
1980
+ version: "0.1.3",
1984
1981
  description: "Universal graph workflow app \u2014 graph language (start/step/decision/while/end), three-table store, session WAL, daemon with claims/leases, four lane adapters (claude/codex/cursor/grok), and a full CLI on four surfaces",
1985
1982
  type: "module",
1986
1983
  main: "./dist/index.js",
@@ -2045,15 +2042,15 @@ var package_default = {
2045
2042
  url: "https://github.com/hasna/apps/issues"
2046
2043
  },
2047
2044
  engines: {
2048
- bun: ">=1.0.0",
2049
- node: ">=20.0.0"
2045
+ bun: ">=1.0.0"
2050
2046
  },
2051
2047
  author: "Hasna",
2052
2048
  license: "Apache-2.0",
2053
2049
  dependencies: {
2054
- "@anthropic-ai/claude-agent-sdk": "^0.3.234",
2050
+ "@anthropic-ai/claude-agent-sdk": "0.3.234",
2055
2051
  "@cursor/sdk": "^1.0.28",
2056
- "@openai/codex-sdk": "^0.147.0",
2052
+ "@hasna/paths": "0.1.0",
2053
+ "@openai/codex-sdk": "0.147.0",
2057
2054
  commander: "^13.1.0"
2058
2055
  },
2059
2056
  devDependencies: {
@@ -2065,6 +2062,33 @@ var package_default = {
2065
2062
  }
2066
2063
  };
2067
2064
 
2065
+ // src/paths.ts
2066
+ import { existsSync as existsSync2 } from "fs";
2067
+ import { homedir } from "os";
2068
+ import { join as join3 } from "path";
2069
+ import { dataDir } from "@hasna/paths";
2070
+ var APP = "workflows";
2071
+ var STORE_DB_FILE = "workflows.db";
2072
+ function getHomeDir(env = process.env) {
2073
+ return env["HOME"] || env["USERPROFILE"] || homedir();
2074
+ }
2075
+ function getLegacyDataDir(env = process.env) {
2076
+ return join3(getHomeDir(env), ".hasna", APP);
2077
+ }
2078
+ function getResolverDataDir(env = process.env) {
2079
+ return dataDir({ app: APP, home: getHomeDir(env), env });
2080
+ }
2081
+ function adoptResolverDataDir(resolved, env = process.env) {
2082
+ const dataOverride = env.HASNA_DATA_HOME;
2083
+ if (typeof dataOverride === "string" && dataOverride.trim().length > 0)
2084
+ return true;
2085
+ return existsSync2(join3(resolved, STORE_DB_FILE));
2086
+ }
2087
+ function getEffectiveDataDir(env = process.env) {
2088
+ const resolved = getResolverDataDir(env);
2089
+ return adoptResolverDataDir(resolved, env) ? resolved : getLegacyDataDir(env);
2090
+ }
2091
+
2068
2092
  // src/service.ts
2069
2093
  var DEFAULT_PORT = 8790;
2070
2094
  var DEFAULT_HOST = "127.0.0.1";
@@ -2087,11 +2111,10 @@ function envInt(names, fallback) {
2087
2111
  return Number.isFinite(parsed) ? parsed : fallback;
2088
2112
  }
2089
2113
  function resolveWorkflowsConfig(overrides = {}) {
2090
- const home = process.env.HOME ?? process.env.USERPROFILE ?? ".";
2091
2114
  return {
2092
2115
  port: overrides.port ?? envInt(["HASNA_WORKFLOWS_PORT", "WORKFLOWS_PORT"], DEFAULT_PORT),
2093
2116
  host: overrides.host ?? envString(["HASNA_WORKFLOWS_HOST", "WORKFLOWS_HOST"]) ?? DEFAULT_HOST,
2094
- dataDir: overrides.dataDir ?? envString(["HASNA_WORKFLOWS_DATA_DIR", "WORKFLOWS_DATA_DIR"]) ?? join3(home, ".hasna", "workflows"),
2117
+ dataDir: overrides.dataDir ?? envString(["HASNA_WORKFLOWS_DATA_DIR", "WORKFLOWS_DATA_DIR"]) ?? getEffectiveDataDir(),
2095
2118
  apiUrl: overrides.apiUrl ?? envString(["WORKFLOWS_API_URL"]),
2096
2119
  apiKey: overrides.apiKey ?? envString(["WORKFLOWS_API_KEY"])
2097
2120
  };
@@ -440,13 +440,10 @@ var init_lanes = __esm(() => {
440
440
  ["grok", createGrokAdapter()]
441
441
  ]);
442
442
  });
443
-
444
- // src/service.ts
445
- import { join as join3 } from "path";
446
443
  // package.json
447
444
  var package_default = {
448
445
  name: "@hasna/workflows",
449
- version: "0.1.1",
446
+ version: "0.1.3",
450
447
  description: "Universal graph workflow app \u2014 graph language (start/step/decision/while/end), three-table store, session WAL, daemon with claims/leases, four lane adapters (claude/codex/cursor/grok), and a full CLI on four surfaces",
451
448
  type: "module",
452
449
  main: "./dist/index.js",
@@ -511,15 +508,15 @@ var package_default = {
511
508
  url: "https://github.com/hasna/apps/issues"
512
509
  },
513
510
  engines: {
514
- bun: ">=1.0.0",
515
- node: ">=20.0.0"
511
+ bun: ">=1.0.0"
516
512
  },
517
513
  author: "Hasna",
518
514
  license: "Apache-2.0",
519
515
  dependencies: {
520
- "@anthropic-ai/claude-agent-sdk": "^0.3.234",
516
+ "@anthropic-ai/claude-agent-sdk": "0.3.234",
521
517
  "@cursor/sdk": "^1.0.28",
522
- "@openai/codex-sdk": "^0.147.0",
518
+ "@hasna/paths": "0.1.0",
519
+ "@openai/codex-sdk": "0.147.0",
523
520
  commander: "^13.1.0"
524
521
  },
525
522
  devDependencies: {
@@ -1502,9 +1499,9 @@ class WorkflowsDaemon {
1502
1499
  }
1503
1500
  const output = { ok: result.ok, exitCode: result.exitCode, output: result.output, durationMs: result.durationMs };
1504
1501
  if (!result.ok) {
1505
- const retriesLeft = (node.maxRetries ?? 0) + 1 - nodeRow.attempts;
1506
- if (retriesLeft > 0) {
1507
- this.store.bumpAttemptsNode(nodeRow.id);
1502
+ const shouldRetry = nodeRow.attempts < (node.maxRetries ?? 0);
1503
+ this.store.bumpAttemptsNode(nodeRow.id);
1504
+ if (shouldRetry) {
1508
1505
  this.store.setRunNodeStatus(nodeRow.id, "pending", { error: result.error ?? `exit ${result.exitCode}` });
1509
1506
  return { kind: "advanced", nodeId };
1510
1507
  }
@@ -1636,17 +1633,44 @@ async function runGraphToCompletion(store, wal, graph, context, options = {}) {
1636
1633
  return store.getRun(run.id);
1637
1634
  }
1638
1635
 
1636
+ // src/paths.ts
1637
+ import { existsSync } from "fs";
1638
+ import { homedir } from "os";
1639
+ import { join } from "path";
1640
+ import { dataDir } from "@hasna/paths";
1641
+ var APP = "workflows";
1642
+ var STORE_DB_FILE = "workflows.db";
1643
+ function getHomeDir(env = process.env) {
1644
+ return env["HOME"] || env["USERPROFILE"] || homedir();
1645
+ }
1646
+ function getLegacyDataDir(env = process.env) {
1647
+ return join(getHomeDir(env), ".hasna", APP);
1648
+ }
1649
+ function getResolverDataDir(env = process.env) {
1650
+ return dataDir({ app: APP, home: getHomeDir(env), env });
1651
+ }
1652
+ function adoptResolverDataDir(resolved, env = process.env) {
1653
+ const dataOverride = env.HASNA_DATA_HOME;
1654
+ if (typeof dataOverride === "string" && dataOverride.trim().length > 0)
1655
+ return true;
1656
+ return existsSync(join(resolved, STORE_DB_FILE));
1657
+ }
1658
+ function getEffectiveDataDir(env = process.env) {
1659
+ const resolved = getResolverDataDir(env);
1660
+ return adoptResolverDataDir(resolved, env) ? resolved : getLegacyDataDir(env);
1661
+ }
1662
+
1639
1663
  // src/store.ts
1640
1664
  import { Database } from "bun:sqlite";
1641
1665
  import { mkdirSync } from "fs";
1642
- import { join } from "path";
1666
+ import { join as join2 } from "path";
1643
1667
  import { randomUUID } from "crypto";
1644
1668
  function nowIso2() {
1645
1669
  return new Date().toISOString();
1646
1670
  }
1647
- function openStore(dataDir) {
1648
- mkdirSync(dataDir, { recursive: true });
1649
- const db = new Database(join(dataDir, "workflows.db"));
1671
+ function openStore(dataDir2) {
1672
+ mkdirSync(dataDir2, { recursive: true });
1673
+ const db = new Database(join2(dataDir2, "workflows.db"));
1650
1674
  db.exec("PRAGMA journal_mode = WAL");
1651
1675
  db.exec("PRAGMA foreign_keys = ON");
1652
1676
  db.exec(`
@@ -1824,8 +1848,8 @@ function openStore(dataDir) {
1824
1848
 
1825
1849
  // src/wal.ts
1826
1850
  import { createHash as createHash3 } from "crypto";
1827
- import { appendFileSync, existsSync, mkdirSync as mkdirSync2, readFileSync, renameSync, truncateSync, writeFileSync } from "fs";
1828
- import { join as join2 } from "path";
1851
+ import { appendFileSync, existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync, renameSync, truncateSync, writeFileSync } from "fs";
1852
+ import { join as join3 } from "path";
1829
1853
  var WAL_FILE_NAME = "session.wal";
1830
1854
  var SESSIONS_DIR_NAME = "sessions";
1831
1855
  var MAX_WAL_BYTES = 256 * 1024 * 1024;
@@ -1840,11 +1864,11 @@ class SessionWAL {
1840
1864
  constructor(filePath) {
1841
1865
  this.filePath = filePath;
1842
1866
  }
1843
- static open(dataDir) {
1844
- mkdirSync2(join2(dataDir, SESSIONS_DIR_NAME), { recursive: true });
1845
- const canonical = join2(dataDir, SESSIONS_DIR_NAME, WAL_FILE_NAME);
1846
- const legacy = join2(dataDir, WAL_FILE_NAME);
1847
- if (existsSync(legacy) && !existsSync(canonical)) {
1867
+ static open(dataDir2) {
1868
+ mkdirSync2(join3(dataDir2, SESSIONS_DIR_NAME), { recursive: true });
1869
+ const canonical = join3(dataDir2, SESSIONS_DIR_NAME, WAL_FILE_NAME);
1870
+ const legacy = join3(dataDir2, WAL_FILE_NAME);
1871
+ if (existsSync2(legacy) && !existsSync2(canonical)) {
1848
1872
  try {
1849
1873
  renameSync(legacy, canonical);
1850
1874
  } catch {
@@ -1975,11 +1999,10 @@ function envInt(names, fallback) {
1975
1999
  return Number.isFinite(parsed) ? parsed : fallback;
1976
2000
  }
1977
2001
  function resolveWorkflowsConfig(overrides = {}) {
1978
- const home = process.env.HOME ?? process.env.USERPROFILE ?? ".";
1979
2002
  return {
1980
2003
  port: overrides.port ?? envInt(["HASNA_WORKFLOWS_PORT", "WORKFLOWS_PORT"], DEFAULT_PORT),
1981
2004
  host: overrides.host ?? envString(["HASNA_WORKFLOWS_HOST", "WORKFLOWS_HOST"]) ?? DEFAULT_HOST,
1982
- dataDir: overrides.dataDir ?? envString(["HASNA_WORKFLOWS_DATA_DIR", "WORKFLOWS_DATA_DIR"]) ?? join3(home, ".hasna", "workflows"),
2005
+ dataDir: overrides.dataDir ?? envString(["HASNA_WORKFLOWS_DATA_DIR", "WORKFLOWS_DATA_DIR"]) ?? getEffectiveDataDir(),
1983
2006
  apiUrl: overrides.apiUrl ?? envString(["WORKFLOWS_API_URL"]),
1984
2007
  apiKey: overrides.apiKey ?? envString(["WORKFLOWS_API_KEY"])
1985
2008
  };
package/dist/service.js CHANGED
@@ -439,13 +439,10 @@ var init_lanes = __esm(() => {
439
439
  ["grok", createGrokAdapter()]
440
440
  ]);
441
441
  });
442
-
443
- // src/service.ts
444
- import { join as join3 } from "path";
445
442
  // package.json
446
443
  var package_default = {
447
444
  name: "@hasna/workflows",
448
- version: "0.1.1",
445
+ version: "0.1.3",
449
446
  description: "Universal graph workflow app \u2014 graph language (start/step/decision/while/end), three-table store, session WAL, daemon with claims/leases, four lane adapters (claude/codex/cursor/grok), and a full CLI on four surfaces",
450
447
  type: "module",
451
448
  main: "./dist/index.js",
@@ -510,15 +507,15 @@ var package_default = {
510
507
  url: "https://github.com/hasna/apps/issues"
511
508
  },
512
509
  engines: {
513
- bun: ">=1.0.0",
514
- node: ">=20.0.0"
510
+ bun: ">=1.0.0"
515
511
  },
516
512
  author: "Hasna",
517
513
  license: "Apache-2.0",
518
514
  dependencies: {
519
- "@anthropic-ai/claude-agent-sdk": "^0.3.234",
515
+ "@anthropic-ai/claude-agent-sdk": "0.3.234",
520
516
  "@cursor/sdk": "^1.0.28",
521
- "@openai/codex-sdk": "^0.147.0",
517
+ "@hasna/paths": "0.1.0",
518
+ "@openai/codex-sdk": "0.147.0",
522
519
  commander: "^13.1.0"
523
520
  },
524
521
  devDependencies: {
@@ -1501,9 +1498,9 @@ class WorkflowsDaemon {
1501
1498
  }
1502
1499
  const output = { ok: result.ok, exitCode: result.exitCode, output: result.output, durationMs: result.durationMs };
1503
1500
  if (!result.ok) {
1504
- const retriesLeft = (node.maxRetries ?? 0) + 1 - nodeRow.attempts;
1505
- if (retriesLeft > 0) {
1506
- this.store.bumpAttemptsNode(nodeRow.id);
1501
+ const shouldRetry = nodeRow.attempts < (node.maxRetries ?? 0);
1502
+ this.store.bumpAttemptsNode(nodeRow.id);
1503
+ if (shouldRetry) {
1507
1504
  this.store.setRunNodeStatus(nodeRow.id, "pending", { error: result.error ?? `exit ${result.exitCode}` });
1508
1505
  return { kind: "advanced", nodeId };
1509
1506
  }
@@ -1635,17 +1632,44 @@ async function runGraphToCompletion(store, wal, graph, context, options = {}) {
1635
1632
  return store.getRun(run.id);
1636
1633
  }
1637
1634
 
1635
+ // src/paths.ts
1636
+ import { existsSync } from "fs";
1637
+ import { homedir } from "os";
1638
+ import { join } from "path";
1639
+ import { dataDir } from "@hasna/paths";
1640
+ var APP = "workflows";
1641
+ var STORE_DB_FILE = "workflows.db";
1642
+ function getHomeDir(env = process.env) {
1643
+ return env["HOME"] || env["USERPROFILE"] || homedir();
1644
+ }
1645
+ function getLegacyDataDir(env = process.env) {
1646
+ return join(getHomeDir(env), ".hasna", APP);
1647
+ }
1648
+ function getResolverDataDir(env = process.env) {
1649
+ return dataDir({ app: APP, home: getHomeDir(env), env });
1650
+ }
1651
+ function adoptResolverDataDir(resolved, env = process.env) {
1652
+ const dataOverride = env.HASNA_DATA_HOME;
1653
+ if (typeof dataOverride === "string" && dataOverride.trim().length > 0)
1654
+ return true;
1655
+ return existsSync(join(resolved, STORE_DB_FILE));
1656
+ }
1657
+ function getEffectiveDataDir(env = process.env) {
1658
+ const resolved = getResolverDataDir(env);
1659
+ return adoptResolverDataDir(resolved, env) ? resolved : getLegacyDataDir(env);
1660
+ }
1661
+
1638
1662
  // src/store.ts
1639
1663
  import { Database } from "bun:sqlite";
1640
1664
  import { mkdirSync } from "fs";
1641
- import { join } from "path";
1665
+ import { join as join2 } from "path";
1642
1666
  import { randomUUID } from "crypto";
1643
1667
  function nowIso2() {
1644
1668
  return new Date().toISOString();
1645
1669
  }
1646
- function openStore(dataDir) {
1647
- mkdirSync(dataDir, { recursive: true });
1648
- const db = new Database(join(dataDir, "workflows.db"));
1670
+ function openStore(dataDir2) {
1671
+ mkdirSync(dataDir2, { recursive: true });
1672
+ const db = new Database(join2(dataDir2, "workflows.db"));
1649
1673
  db.exec("PRAGMA journal_mode = WAL");
1650
1674
  db.exec("PRAGMA foreign_keys = ON");
1651
1675
  db.exec(`
@@ -1823,8 +1847,8 @@ function openStore(dataDir) {
1823
1847
 
1824
1848
  // src/wal.ts
1825
1849
  import { createHash as createHash3 } from "crypto";
1826
- import { appendFileSync, existsSync, mkdirSync as mkdirSync2, readFileSync, renameSync, truncateSync, writeFileSync } from "fs";
1827
- import { join as join2 } from "path";
1850
+ import { appendFileSync, existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync, renameSync, truncateSync, writeFileSync } from "fs";
1851
+ import { join as join3 } from "path";
1828
1852
  var WAL_FILE_NAME = "session.wal";
1829
1853
  var SESSIONS_DIR_NAME = "sessions";
1830
1854
  var MAX_WAL_BYTES = 256 * 1024 * 1024;
@@ -1839,11 +1863,11 @@ class SessionWAL {
1839
1863
  constructor(filePath) {
1840
1864
  this.filePath = filePath;
1841
1865
  }
1842
- static open(dataDir) {
1843
- mkdirSync2(join2(dataDir, SESSIONS_DIR_NAME), { recursive: true });
1844
- const canonical = join2(dataDir, SESSIONS_DIR_NAME, WAL_FILE_NAME);
1845
- const legacy = join2(dataDir, WAL_FILE_NAME);
1846
- if (existsSync(legacy) && !existsSync(canonical)) {
1866
+ static open(dataDir2) {
1867
+ mkdirSync2(join3(dataDir2, SESSIONS_DIR_NAME), { recursive: true });
1868
+ const canonical = join3(dataDir2, SESSIONS_DIR_NAME, WAL_FILE_NAME);
1869
+ const legacy = join3(dataDir2, WAL_FILE_NAME);
1870
+ if (existsSync2(legacy) && !existsSync2(canonical)) {
1847
1871
  try {
1848
1872
  renameSync(legacy, canonical);
1849
1873
  } catch {
@@ -1974,11 +1998,10 @@ function envInt(names, fallback) {
1974
1998
  return Number.isFinite(parsed) ? parsed : fallback;
1975
1999
  }
1976
2000
  function resolveWorkflowsConfig(overrides = {}) {
1977
- const home = process.env.HOME ?? process.env.USERPROFILE ?? ".";
1978
2001
  return {
1979
2002
  port: overrides.port ?? envInt(["HASNA_WORKFLOWS_PORT", "WORKFLOWS_PORT"], DEFAULT_PORT),
1980
2003
  host: overrides.host ?? envString(["HASNA_WORKFLOWS_HOST", "WORKFLOWS_HOST"]) ?? DEFAULT_HOST,
1981
- dataDir: overrides.dataDir ?? envString(["HASNA_WORKFLOWS_DATA_DIR", "WORKFLOWS_DATA_DIR"]) ?? join3(home, ".hasna", "workflows"),
2004
+ dataDir: overrides.dataDir ?? envString(["HASNA_WORKFLOWS_DATA_DIR", "WORKFLOWS_DATA_DIR"]) ?? getEffectiveDataDir(),
1982
2005
  apiUrl: overrides.apiUrl ?? envString(["WORKFLOWS_API_URL"]),
1983
2006
  apiKey: overrides.apiKey ?? envString(["WORKFLOWS_API_KEY"])
1984
2007
  };
@@ -4,7 +4,7 @@
4
4
  "class": "service",
5
5
  "contractVersion": "v1",
6
6
  "kitVersion": "0.14.1",
7
- "description": "Universal graph workflow app: graph language (start/step/decision/while/end), three-table run store, session WAL, claims/leases daemon, four lane adapters (claude, codex, cursor, grok), and a full CLI on four surfaces (CLI, MCP, serve, SDK).",
7
+ "description": "Universal graph workflow app: graph language (start/step/decision/while/end), three-table run store, session WAL, claims/leases daemon, four lane adapters (claude, codex, cursor, grok), and a full CLI on four surfaces (CLI, MCP, serve, SDK). Local state lives at the effective workflows data home (~/.hasna/workflows by default, resolved via @hasna/paths; the XDG data home is adopted once the store is migrated there or HASNA_DATA_HOME is set).",
8
8
  "bins": [
9
9
  "workflows",
10
10
  "workflows-mcp",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/workflows",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "Universal graph workflow app — graph language (start/step/decision/while/end), three-table store, session WAL, daemon with claims/leases, four lane adapters (claude/codex/cursor/grok), and a full CLI on four surfaces",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -65,15 +65,15 @@
65
65
  "url": "https://github.com/hasna/apps/issues"
66
66
  },
67
67
  "engines": {
68
- "bun": ">=1.0.0",
69
- "node": ">=20.0.0"
68
+ "bun": ">=1.0.0"
70
69
  },
71
70
  "author": "Hasna",
72
71
  "license": "Apache-2.0",
73
72
  "dependencies": {
74
- "@anthropic-ai/claude-agent-sdk": "^0.3.234",
73
+ "@anthropic-ai/claude-agent-sdk": "0.3.234",
75
74
  "@cursor/sdk": "^1.0.28",
76
- "@openai/codex-sdk": "^0.147.0",
75
+ "@hasna/paths": "0.1.0",
76
+ "@openai/codex-sdk": "0.147.0",
77
77
  "commander": "^13.1.0"
78
78
  },
79
79
  "devDependencies": {
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Workflows data-home resolution — the effective local data home for the
3
+ * @hasna/workflows package, resolved through @hasna/paths.
4
+ *
5
+ * Resolution order:
6
+ * 1. exact-app override — `HASNA_WORKFLOWS_DATA_DIR` / `WORKFLOWS_DATA_DIR`
7
+ * (the shipped overrides, applied in `resolveWorkflowsConfig`) — wins
8
+ * unconditionally;
9
+ * 2. the @hasna/paths data home (XDG / macOS home layout) once adopted;
10
+ * 3. otherwise the legacy `~/.hasna/workflows` default.
11
+ *
12
+ * Gated legacy adoption: the legacy `~/.hasna/workflows` home stays effective
13
+ * until the store has actually been migrated to the XDG data home
14
+ * (`workflows.db` exists at the resolver home) or the operator sets the
15
+ * data-kind override `HASNA_DATA_HOME`. An existing local store never becomes
16
+ * invisible on upgrade.
17
+ */
18
+ /** The workflows app slug used by the @hasna/paths resolver. */
19
+ export declare const APP: "workflows";
20
+ /** The store database file name, the marker that a store lives at a data home. */
21
+ export declare const STORE_DB_FILE = "workflows.db";
22
+ /**
23
+ * The effective user home, mirroring the pre-existing resolution
24
+ * (`HOME` || `USERPROFILE` || os.homedir()).
25
+ */
26
+ export declare function getHomeDir(env?: NodeJS.ProcessEnv): string;
27
+ /** Pre-XDG legacy workflows data home: ~/.hasna/workflows. */
28
+ export declare function getLegacyDataDir(env?: NodeJS.ProcessEnv): string;
29
+ /**
30
+ * The @hasna/paths-resolved workflows data home (XDG / macOS home layout):
31
+ * `~/.local/share/hasna/workflows` on Linux, `~/Library/Application
32
+ * Support/Hasna/workflows` on macOS. The home override mirrors the
33
+ * pre-existing `$HOME`-first resolution so the resolver follows the same home
34
+ * the legacy path does.
35
+ */
36
+ export declare function getResolverDataDir(env?: NodeJS.ProcessEnv): string;
37
+ /**
38
+ * Whether the resolver (XDG) data home should be adopted as the effective
39
+ * workflows data home. The resolver home is adopted only when the operator
40
+ * has set `HASNA_DATA_HOME` (the data-kind override — a deliberate opt-in to
41
+ * the XDG layout) or the store has already been physically migrated there
42
+ * (`workflows.db` exists). A machine that only redirects another kind (e.g.
43
+ * cache to tmpfs) must NOT have its data home moved, and a live store at the
44
+ * legacy home must never become invisible on upgrade.
45
+ */
46
+ export declare function adoptResolverDataDir(resolved: string, env?: NodeJS.ProcessEnv): boolean;
47
+ /**
48
+ * The effective workflows data home: the resolver home once adopted,
49
+ * otherwise the legacy `~/.hasna/workflows` default.
50
+ */
51
+ export declare function getEffectiveDataDir(env?: NodeJS.ProcessEnv): string;
package/types/types.d.ts CHANGED
@@ -12,7 +12,9 @@ export interface WorkflowsConfig {
12
12
  port: number;
13
13
  /** Bind host for workflows-serve. Default 127.0.0.1. */
14
14
  host: string;
15
- /** Data directory (store slice lands the db here). Default ~/.hasna/workflows. */
15
+ /** Data directory (store slice lands the db here). Default ~/.hasna/workflows,
16
+ * resolved through @hasna/paths once the XDG data home is adopted
17
+ * (HASNA_DATA_HOME set or the store migrated there). */
16
18
  dataDir: string;
17
19
  /** Optional client API URL (WORKFLOWS_API_URL) — consumed by later slices. */
18
20
  apiUrl?: string;