@hasna/domains 0.0.47 → 0.1.0

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 (55) hide show
  1. package/README.md +58 -34
  2. package/dist/cli/commands/dns.d.ts.map +1 -1
  3. package/dist/cli/commands/doctor.d.ts.map +1 -1
  4. package/dist/cli/commands/serve.d.ts +1 -0
  5. package/dist/cli/commands/serve.d.ts.map +1 -1
  6. package/dist/cli/index.js +4754 -2105
  7. package/dist/db/database.d.ts +6 -23
  8. package/dist/db/database.d.ts.map +1 -1
  9. package/dist/db/domains.d.ts +3 -14
  10. package/dist/db/domains.d.ts.map +1 -1
  11. package/dist/db/store.d.ts +36 -32
  12. package/dist/db/store.d.ts.map +1 -1
  13. package/dist/generated/storage-kit/backend.d.ts +4 -4
  14. package/dist/generated/storage-kit/backend.d.ts.map +1 -1
  15. package/dist/generated/storage-kit/index.d.ts +1 -1
  16. package/dist/generated/storage-kit/index.d.ts.map +1 -1
  17. package/dist/generated/storage-kit/migrations.d.ts.map +1 -1
  18. package/dist/generated/storage-kit/pool.d.ts +2 -5
  19. package/dist/generated/storage-kit/pool.d.ts.map +1 -1
  20. package/dist/index.d.ts +1 -1
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.js +1127 -1734
  23. package/dist/lib/app-home.d.ts +32 -44
  24. package/dist/lib/app-home.d.ts.map +1 -1
  25. package/dist/lib/client-storage-policy.d.ts +10 -0
  26. package/dist/lib/client-storage-policy.d.ts.map +1 -0
  27. package/dist/lib/client-types.d.ts +184 -0
  28. package/dist/lib/client-types.d.ts.map +1 -0
  29. package/dist/lib/cloudflare.d.ts.map +1 -1
  30. package/dist/lib/cloudflare.js +23 -0
  31. package/dist/lib/config.d.ts +6 -16
  32. package/dist/lib/config.d.ts.map +1 -1
  33. package/dist/lib/dns-plan.d.ts +1 -1
  34. package/dist/lib/dns-plan.d.ts.map +1 -1
  35. package/dist/lib/domains-resolver.d.ts +96 -0
  36. package/dist/lib/domains-resolver.d.ts.map +1 -0
  37. package/dist/lib/registrar.d.ts +8 -0
  38. package/dist/lib/registrar.d.ts.map +1 -1
  39. package/dist/lib/registrar.js +23 -0
  40. package/dist/mcp/harness.d.ts +46 -0
  41. package/dist/mcp/harness.d.ts.map +1 -0
  42. package/dist/mcp/http.d.ts +3 -2
  43. package/dist/mcp/http.d.ts.map +1 -1
  44. package/dist/mcp/index.d.ts.map +1 -1
  45. package/dist/mcp/index.js +24162 -25378
  46. package/dist/mcp/tool-filter.d.ts +6 -0
  47. package/dist/mcp/tool-filter.d.ts.map +1 -1
  48. package/dist/sdk/index.d.ts +21 -10
  49. package/dist/sdk/index.d.ts.map +1 -1
  50. package/dist/sdk/index.js +813 -275
  51. package/dist/server/index.d.ts +6 -0
  52. package/dist/server/index.d.ts.map +1 -1
  53. package/dist/server/index.js +84 -29
  54. package/package.json +5 -7
  55. package/postinstall.js +13 -14
@@ -15,4 +15,10 @@ export declare const SIGNING_KEY_ENVS: readonly ["HASNA_DOMAINS_API_SIGNING_KEY"
15
15
  /** Normalize the module-injected env names to the kit/auth conventions. */
16
16
  export declare function normalizeEnv(env?: NodeJS.ProcessEnv): void;
17
17
  export declare function resolveSigningSecret(env?: NodeJS.ProcessEnv): string;
18
+ export declare const USAGE = "domains-serve \u2014 standalone HTTP API server for @hasna/domains (PostgreSQL backend)\n\nUsage: domains-serve [--port <n>] [--host <addr>]\n\nOptions:\n --port <n> Port to listen on (default: $PORT or 8080)\n --host <addr> Address to bind (default: $HOST or 0.0.0.0)\n -h, --help Print this help and exit\n -V, --version Print the package version and exit\n\nEnvironment:\n HASNA_DOMAINS_DATABASE_URL PostgreSQL DSN (falls back to DATABASE_URL)\n HASNA_DOMAINS_API_SIGNING_KEY HMAC signing secret for API keys\n (falls back to API_KEY_SIGNING_SECRET)";
19
+ /** Shared authenticated server for both domains serve and domains-serve. */
20
+ export declare function startDomainsServer(options: {
21
+ port: number;
22
+ host: string;
23
+ }): Promise<void>;
18
24
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;GAWG;AASH,eAAO,MAAM,gBAAgB,+FAInB,CAAC;AAEX,2EAA2E;AAC3E,wBAAgB,YAAY,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,IAAI,CAIvE;AAED,wBAAgB,oBAAoB,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,MAAM,CAQjF"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;GAWG;AASH,eAAO,MAAM,gBAAgB,+FAInB,CAAC;AAEX,2EAA2E;AAC3E,wBAAgB,YAAY,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,IAAI,CAIvE;AAED,wBAAgB,oBAAoB,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,MAAM,CAQjF;AAUD,eAAO,MAAM,KAAK,0mBAasD,CAAC;AAwBzE,4EAA4E;AAC5E,wBAAsB,kBAAkB,CAAC,OAAO,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAgD/F"}
@@ -79,7 +79,7 @@ function viewWindow(view) {
79
79
  }
80
80
  function toBuffer(secret) {
81
81
  if (typeof secret === "string")
82
- return Buffer.from(secret, "utf8");
82
+ return Buffer.from(secret.trim(), "utf8");
83
83
  if (ArrayBuffer.isView(secret)) {
84
84
  const [store, byteOffset, byteLength] = viewWindow(secret);
85
85
  return Buffer.from(store, byteOffset, byteLength);
@@ -386,11 +386,17 @@ class ApiKeyStore {
386
386
  return status !== "active";
387
387
  };
388
388
  }
389
- async revoke(kid, reason, atMs = Date.now()) {
389
+ async revoke(kid, reason, atMs = Date.now(), options = {}) {
390
+ const params = [kid, new Date(atMs).toISOString(), reason ?? null];
391
+ let scope = "";
392
+ if (options.app !== undefined) {
393
+ params.push(options.app);
394
+ scope = ` AND app = $${params.length}`;
395
+ }
390
396
  const row = await this.client.get(`UPDATE ${this.table}
391
397
  SET revoked_at = COALESCE(revoked_at, $2), revoked_reason = COALESCE(revoked_reason, $3)
392
- WHERE kid = $1
393
- RETURNING kid`, [kid, new Date(atMs).toISOString(), reason ?? null]);
398
+ WHERE kid = $1${scope}
399
+ RETURNING kid`, params);
394
400
  return row !== null;
395
401
  }
396
402
  async touchLastUsed(kid, atMs = Date.now()) {
@@ -629,24 +635,43 @@ function serverDataBackendEnvKeys(name) {
629
635
  databaseUrlKeys: [`HASNA_${token}_DATABASE_URL`, `${token}_DATABASE_URL`]
630
636
  };
631
637
  }
632
- function firstEnv(env, keys) {
633
- for (const key of keys) {
634
- const value = ownString(env, key)?.trim();
635
- if (value)
636
- return { key, value };
638
+ function definedDatabaseUrlEntries(env, keys) {
639
+ return keys.map((key) => ({ key, value: ownString(env, key) })).filter((entry) => entry.value !== undefined);
640
+ }
641
+ function assertPostgresqlDatabaseUrl(name, entries) {
642
+ const canonicalKey = serverDataBackendEnvKeys(name).databaseUrlKeys[0];
643
+ if (entries.length === 0) {
644
+ throw new Error(`${canonicalKey} is required; servers use PostgreSQL and never default to SQLite.`);
637
645
  }
638
- return null;
646
+ const blank = entries.filter((entry) => entry.value.trim().length === 0);
647
+ if (blank.length > 0) {
648
+ throw new Error(`${blank.map((entry) => entry.key).join(" and ")} is set but blank; a PostgreSQL database URL is required.`);
649
+ }
650
+ const controlled = entries.find((entry) => /[\u0000-\u001f\u007f]/.test(entry.value));
651
+ if (controlled)
652
+ throw new Error(`${controlled.key} must not contain ASCII control characters.`);
653
+ const normalized = entries.map((entry) => ({ key: entry.key, value: entry.value.trim() }));
654
+ if (normalized.length > 1 && new Set(normalized.map((entry) => entry.value)).size > 1) {
655
+ throw new Error(`${normalized.map((entry) => entry.key).join(" and ")} disagree; database URL aliases must be identical or only one may be set.`);
656
+ }
657
+ const selected = normalized[0];
658
+ let parsed;
659
+ try {
660
+ parsed = new URL(selected.value);
661
+ } catch {
662
+ throw new Error(`${selected.key} must be an absolute PostgreSQL connection URL.`);
663
+ }
664
+ if (parsed.protocol !== "postgres:" && parsed.protocol !== "postgresql:") {
665
+ throw new Error(`${selected.key} must use the postgres or postgresql scheme.`);
666
+ }
667
+ if (!parsed.hostname || parsed.pathname.length <= 1) {
668
+ throw new Error(`${selected.key} must name a PostgreSQL host and database.`);
669
+ }
670
+ return selected;
639
671
  }
640
672
  function resolveServerDataBackend(name, env = process.env) {
641
- const databaseUrl = firstEnv(env, serverDataBackendEnvKeys(name).databaseUrlKeys);
642
- if (!databaseUrl) {
643
- return {
644
- backend: "sqlite",
645
- source: "default",
646
- databaseUrlPresent: false,
647
- databaseUrlSource: null
648
- };
649
- }
673
+ const keys = serverDataBackendEnvKeys(name).databaseUrlKeys;
674
+ const databaseUrl = assertPostgresqlDatabaseUrl(name, definedDatabaseUrlEntries(env, keys));
650
675
  return {
651
676
  backend: "postgresql",
652
677
  source: databaseUrl.key,
@@ -655,8 +680,8 @@ function resolveServerDataBackend(name, env = process.env) {
655
680
  };
656
681
  }
657
682
  function resolveDatabaseUrl(name, env = process.env) {
658
- const hit = firstEnv(env, serverDataBackendEnvKeys(name).databaseUrlKeys);
659
- return hit?.value ?? null;
683
+ const keys = serverDataBackendEnvKeys(name).databaseUrlKeys;
684
+ return assertPostgresqlDatabaseUrl(name, definedDatabaseUrlEntries(env, keys)).value;
660
685
  }
661
686
  // src/generated/storage-kit/tls.ts
662
687
  import { readFileSync } from "fs";
@@ -903,9 +928,6 @@ function createServerPoolFromEnv(appName, options = {}) {
903
928
  const env = own.env ?? process.env;
904
929
  const resolution = resolveServerDataBackend(appName, env);
905
930
  const connectionString = resolveDatabaseUrl(appName, env);
906
- if (!connectionString) {
907
- throw new Error(`postgresql storage for ${appName} needs a database URL. Set ` + `HASNA_${appName.toUpperCase().replace(/-/g, "_")}_DATABASE_URL.`);
908
- }
909
931
  const pool = createPgPool({
910
932
  ...own,
911
933
  connectionString,
@@ -913,7 +935,7 @@ function createServerPoolFromEnv(appName, options = {}) {
913
935
  });
914
936
  return {
915
937
  client: createQueryClient(pool),
916
- connectionSource: resolution.databaseUrlSource ?? "unknown"
938
+ connectionSource: resolution.databaseUrlSource
917
939
  };
918
940
  }
919
941
  // src/generated/storage-kit/migrations.ts
@@ -2680,11 +2702,44 @@ function parseArg(name, fallback) {
2680
2702
  return process.argv[idx + 1];
2681
2703
  return fallback;
2682
2704
  }
2705
+ var USAGE = `domains-serve \u2014 standalone HTTP API server for @hasna/domains (PostgreSQL backend)
2706
+
2707
+ Usage: domains-serve [--port <n>] [--host <addr>]
2708
+
2709
+ Options:
2710
+ --port <n> Port to listen on (default: $PORT or ${DEFAULT_PORT})
2711
+ --host <addr> Address to bind (default: $HOST or 0.0.0.0)
2712
+ -h, --help Print this help and exit
2713
+ -V, --version Print the package version and exit
2714
+
2715
+ Environment:
2716
+ HASNA_DOMAINS_DATABASE_URL PostgreSQL DSN (falls back to DATABASE_URL)
2717
+ HASNA_DOMAINS_API_SIGNING_KEY HMAC signing secret for API keys
2718
+ (falls back to API_KEY_SIGNING_SECRET)`;
2719
+ function earlyArgAnswer(argv) {
2720
+ if (argv.includes("--help") || argv.includes("-h"))
2721
+ return USAGE;
2722
+ if (argv.includes("--version") || argv.includes("-V"))
2723
+ return getPackageVersion();
2724
+ return;
2725
+ }
2683
2726
  async function main() {
2684
- if (process.argv.includes("--version") || process.argv.includes("-V")) {
2685
- console.log(getPackageVersion());
2727
+ const early = earlyArgAnswer(process.argv);
2728
+ if (early !== undefined) {
2729
+ console.log(early);
2686
2730
  return;
2687
2731
  }
2732
+ await startDomainsServer({
2733
+ port: Number(parseArg("--port", process.env["PORT"]) ?? DEFAULT_PORT),
2734
+ host: parseArg("--host", process.env["HOST"]) ?? "0.0.0.0"
2735
+ });
2736
+ }
2737
+ async function startDomainsServer(options) {
2738
+ const { port, host } = options;
2739
+ if (!Number.isInteger(port) || port < 1 || port > 65535)
2740
+ throw new Error("Port must be an integer from 1 to 65535.");
2741
+ if (!host.trim())
2742
+ throw new Error("Host must not be blank.");
2688
2743
  normalizeEnv();
2689
2744
  const version = getPackageVersion();
2690
2745
  const signingSecret = resolveSigningSecret();
@@ -2704,8 +2759,6 @@ async function main() {
2704
2759
  }
2705
2760
  }
2706
2761
  });
2707
- const port = Number(parseArg("--port", process.env["PORT"]) ?? DEFAULT_PORT) || DEFAULT_PORT;
2708
- const host = parseArg("--host", process.env["HOST"]) ?? "0.0.0.0";
2709
2762
  Bun.serve({
2710
2763
  port,
2711
2764
  hostname: host,
@@ -2728,7 +2781,9 @@ if (import.meta.main) {
2728
2781
  });
2729
2782
  }
2730
2783
  export {
2784
+ startDomainsServer,
2731
2785
  resolveSigningSecret,
2732
2786
  normalizeEnv,
2787
+ USAGE,
2733
2788
  SIGNING_KEY_ENVS
2734
2789
  };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@hasna/domains",
3
- "version": "0.0.47",
4
- "description": "Domain portfolio, registrar, marketplace, and DNS management for AI agents — CLI + MCP + HTTP API + SDK, local SQLite or cloud Postgres",
3
+ "version": "0.1.0",
4
+ "description": "Domain portfolio, registrar, marketplace, and DNS management for AI agents — CLI + MCP + HTTP API + SDK, shared account API and PostgreSQL",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
7
7
  "types": "dist/index.d.ts",
@@ -63,7 +63,7 @@
63
63
  "prepublishOnly": "bun run build",
64
64
  "postinstall": "node postinstall.js",
65
65
  "prepack": "bun run build && bun run artifact-scan",
66
- "artifact-scan": "bun pm pack --ignore-scripts --quiet --filename \"$PWD/domains-artifact-scan.tgz\" && bunx @hasna/contracts@0.14.1 artifact-scan domains-artifact-scan.tgz; rc=$?; rm -f \"$PWD/domains-artifact-scan.tgz\"; exit $rc"
66
+ "artifact-scan": "bun pm pack --ignore-scripts --quiet --filename \"$PWD/domains-artifact-scan.tgz\" && bunx @hasna/contracts@1.0.2 artifact-scan domains-artifact-scan.tgz; rc=$?; rm -f \"$PWD/domains-artifact-scan.tgz\"; exit $rc"
67
67
  },
68
68
  "keywords": [
69
69
  "domains",
@@ -98,8 +98,6 @@
98
98
  "@aws-sdk/client-route-53": "^3.1067.0",
99
99
  "@aws-sdk/client-route-53-domains": "^3.1067.0",
100
100
  "@aws-sdk/credential-provider-ini": "3.972.53",
101
- "@hasna/contracts": "0.14.1",
102
- "@hasna/paths": "0.1.0",
103
101
  "@modelcontextprotocol/sdk": "^1.29.0",
104
102
  "chalk": "^5.4.1",
105
103
  "commander": "^13.1.0",
@@ -121,8 +119,8 @@
121
119
  }
122
120
  },
123
121
  "devDependencies": {
124
- "@hasna/mcp-harness": "0.1.0",
125
- "@types/bun": "^1.3.14",
122
+ "@hasna/contracts": "1.0.2",
123
+ "@types/bun": "1.3.14",
126
124
  "@types/pg": "^8.20.0",
127
125
  "@types/react": "^18.3.18",
128
126
  "bun-types": "1.3.14",
package/postinstall.js CHANGED
@@ -1,34 +1,33 @@
1
- // Best-effort install-time creation of the domains home directory, resolving
2
- // the SAME effective home the runtime uses (src/lib/app-home.ts): an exact-app
3
- // override (HASNA_DOMAINS_HOME / DOMAINS_HOME / HASNA_DOMAINS_DIR / DOMAINS_DIR)
4
- // wins; otherwise the @hasna/paths XDG data home once adopted (HASNA_DATA_HOME
5
- // set, or domains.db already migrated there); otherwise the legacy
6
- // ~/.hasna/domains default. Failures are non-fatal: the runtime creates the
7
- // same directory on first use.
1
+ // Best-effort install-time creation of the domains LOCAL DATA home directory,
2
+ // resolving the SAME effective home the runtime uses (src/lib/app-home.ts):
3
+ // an exact-app override (HASNA_DOMAINS_HOME / HASNA_DOMAINS_DIR, then the
4
+ // legacy unprefixed DOMAINS_HOME / DOMAINS_DIR aliases) wins; otherwise
5
+ // $HASNA_HOME/domains (the shared root override); otherwise the legacy
6
+ // ~/.hasna/domains default. Faithfully mirrors the runtime module — no
7
+ // @hasna/paths, no XDG, no HASNA_*_HOME kind overrides (stripped, hasna/apps#1720
8
+ // class B). Failures are non-fatal: the runtime creates the same directory on
9
+ // first use.
8
10
  import { chmodSync, existsSync, mkdirSync } from "node:fs";
9
11
  import { homedir } from "node:os";
10
12
  import { join } from "node:path";
11
13
 
12
14
  try {
13
- const { dataDir } = await import("@hasna/paths");
14
15
  const env = process.env;
15
16
  const override = (
16
17
  env["HASNA_DOMAINS_HOME"] ||
17
- env["DOMAINS_HOME"] ||
18
18
  env["HASNA_DOMAINS_DIR"] ||
19
+ env["DOMAINS_HOME"] ||
19
20
  env["DOMAINS_DIR"] ||
20
21
  ""
21
22
  ).trim();
22
- const dataHomeOverride = (env["HASNA_DATA_HOME"] || "").trim();
23
23
  const home = env["HOME"] || env["USERPROFILE"] || homedir();
24
24
 
25
25
  let dir;
26
26
  if (override) {
27
27
  dir = override;
28
28
  } else {
29
- const resolved = dataDir({ app: "domains", home, env });
30
- const adopted = Boolean(dataHomeOverride) || existsSync(join(resolved, "domains.db"));
31
- dir = adopted ? resolved : join(home, ".hasna", "domains");
29
+ const hasnaHome = (env["HASNA_HOME"] || "").trim();
30
+ dir = hasnaHome ? join(hasnaHome, "domains") : join(home, ".hasna", "domains");
32
31
  }
33
32
 
34
33
  if (!existsSync(dir)) mkdirSync(dir, { recursive: true, mode: 0o700 });
@@ -39,4 +38,4 @@ try {
39
38
  }
40
39
  } catch {
41
40
  // never fail an install over pre-created directories
42
- }
41
+ }