openship 0.6.7 → 0.6.8

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 (67) hide show
  1. package/dist/bare-FA66N7GC.js +29 -0
  2. package/dist/{chunk-LHFXSC36.js → chunk-3MP6ACKM.js} +183 -87
  3. package/dist/{chunk-MEE6OZFP.js → chunk-5DJOB7IX.js} +3 -3
  4. package/dist/{chunk-PPEBIY5F.js → chunk-5I3COAFH.js} +35 -12
  5. package/dist/{chunk-VQM6JHJB.js → chunk-5UXHT6ED.js} +9 -9
  6. package/dist/{chunk-XWHWTHEL.js → chunk-6GU6JSK6.js} +21 -2
  7. package/dist/{chunk-QHMDCYZ3.js → chunk-7PEXM2UI.js} +213 -45
  8. package/dist/{chunk-RDXT4BQB.js → chunk-7YBWXEM6.js} +4 -4
  9. package/dist/{chunk-H4FS5BED.js → chunk-AHJYP7DG.js} +13 -11
  10. package/dist/{chunk-VSTFHUCV.js → chunk-BO6CY4V2.js} +109 -23
  11. package/dist/{chunk-J3K6XABC.js → chunk-D3OEMXYP.js} +3 -3
  12. package/dist/{chunk-UWEV6XTS.js → chunk-D6HU4VUZ.js} +1 -1
  13. package/dist/{chunk-S6B2Z3OO.js → chunk-ENKJ7XIF.js} +21 -11
  14. package/dist/{chunk-BLEXTYRK.js → chunk-GNOMEMNI.js} +1 -1
  15. package/dist/{chunk-NRTJRNAE.js → chunk-IAMOUDHY.js} +138 -54
  16. package/dist/{chunk-CIJV43LH.js → chunk-MUWVC5R4.js} +161 -39
  17. package/dist/{chunk-ZPJ74LXE.js → chunk-NIYD7MD2.js} +197 -45
  18. package/dist/{chunk-FZ6QCF6B.js → chunk-NPSRGIHV.js} +2 -2
  19. package/dist/{chunk-2GWYYHOR.js → chunk-NRU2EBYZ.js} +2 -2
  20. package/dist/{chunk-J2XM3QSP.js → chunk-NUT6HKK6.js} +5 -5
  21. package/dist/{chunk-H3WT3A6R.js → chunk-P7RCYELG.js} +1 -1
  22. package/dist/{chunk-FYPFEDPZ.js → chunk-R5DC5PEF.js} +1 -1
  23. package/dist/{chunk-DDFUESPJ.js → chunk-RCMERVBL.js} +9 -9
  24. package/dist/{chunk-RXQBD4ZY.js → chunk-RHLNVZMS.js} +2 -2
  25. package/dist/{chunk-WU54BXFE.js → chunk-SK4FFCFR.js} +1 -1
  26. package/dist/{chunk-PVP4RGWT.js → chunk-T46BQOLY.js} +3 -3
  27. package/dist/{chunk-E2X26PXR.js → chunk-T6DVUX4F.js} +1 -1
  28. package/dist/{chunk-5T4UVOM3.js → chunk-TAZZZKYO.js} +1 -1
  29. package/dist/{chunk-GGIAIGUL.js → chunk-UAWY6X7V.js} +34 -12
  30. package/dist/{chunk-2ANV5ZQ5.js → chunk-VAI5VOOI.js} +1 -1
  31. package/dist/{chunk-2VSW6Q6C.js → chunk-WEZFTL4V.js} +655 -62
  32. package/dist/{chunk-GIPJNNI5.js → chunk-X53JXRS4.js} +3 -3
  33. package/dist/{cloud-KRTZ2IL5.js → cloud-2CRDKOWQ.js} +1 -1
  34. package/dist/cloud-REV3DPJH.js +42 -0
  35. package/dist/{detect-G4DR3U2N.js → detect-BBFT5GEU.js} +4 -4
  36. package/dist/{docker-YOVPCIB4.js → docker-EG3QSE6I.js} +10 -10
  37. package/dist/{docker-edge-executor-QFXD3337.js → docker-edge-executor-E5TOTLMR.js} +7 -7
  38. package/dist/edge-import-YJVJUGYC.js +56 -0
  39. package/dist/{edge-preflight-MXERVAMG.js → edge-preflight-QUW4BM7N.js} +13 -13
  40. package/dist/{ensure-container-edge-F4JUNQIK.js → ensure-container-edge-FOEFQ3PI.js} +13 -11
  41. package/dist/{executor-CVPAYYHR.js → executor-NTWDJ3DB.js} +6 -6
  42. package/dist/host-channel-preflight-OMCZMMKR.js +54 -0
  43. package/dist/index.js +158 -72
  44. package/dist/{local-executor-ODC6RANU.js → local-executor-OQ2GRN7K.js} +3 -3
  45. package/dist/{nginx-KLFF43TC.js → nginx-4U4C2XUS.js} +9 -9
  46. package/dist/{noop-NJ72F46T.js → noop-CLLJHCPL.js} +1 -1
  47. package/dist/{openresty-lua-VKWUV4GB.js → openresty-lua-LLWT5XQW.js} +7 -5
  48. package/dist/{privilege-7D3SSOL3.js → privilege-MNDVMDST.js} +5 -5
  49. package/dist/server/index.js +31643 -25553
  50. package/dist/server/migrations/0109_service_build_args.sql +9 -0
  51. package/dist/server/migrations/0110_service_deployment_host_ports.sql +18 -0
  52. package/dist/server/migrations/0111_host_port_claim.sql +381 -0
  53. package/dist/server/migrations/0112_backup_run_execution_lease.sql +12 -0
  54. package/dist/server/migrations/0113_docker_migration_execution_lease.sql +11 -0
  55. package/dist/server/migrations/0114_build_session_execution_lease_index.sql +6 -0
  56. package/dist/server/migrations/0115_orphan_target_key.sql +3 -0
  57. package/dist/server/migrations/0116_domain_ssl_challenge.sql +2 -0
  58. package/dist/server/migrations/meta/_journal.json +56 -0
  59. package/dist/{setup-I5AJC6NJ.js → setup-IFIMPQZJ.js} +12 -12
  60. package/dist/{ssh-client-625VWGKP.js → ssh-client-2XWZCMGI.js} +2 -2
  61. package/dist/up-HQS7IR7H.js +61 -0
  62. package/package.json +1 -1
  63. package/dist/bare-7QD623QV.js +0 -29
  64. package/dist/cloud-KNNABE3O.js +0 -40
  65. package/dist/edge-import-CRFVSR6P.js +0 -56
  66. package/dist/host-channel-preflight-QTDNGLCK.js +0 -54
  67. package/dist/up-QXTOOVXC.js +0 -61
@@ -1017,6 +1017,65 @@ function ownsNetworkEndpoint(resolvedNetworkMode) {
1017
1017
  return !resolvedNetworkMode;
1018
1018
  }
1019
1019
 
1020
+ // ../../packages/core/src/utils.ts
1021
+ var REDIRECT_STATUSES = [301, 302, 307, 308];
1022
+ var DEFAULT_REDIRECT_STATUS = 301;
1023
+ function resolveRedirectStatus(status) {
1024
+ return REDIRECT_STATUSES.includes(status) ? status : DEFAULT_REDIRECT_STATUS;
1025
+ }
1026
+ function isValidEmail(value) {
1027
+ const email3 = value?.trim() ?? "";
1028
+ if (!email3 || email3.length > 254) return false;
1029
+ return /^[A-Za-z0-9._%+-]+@[A-Za-z0-9-]+(\.[A-Za-z0-9-]+)*\.[A-Za-z]{2,}$/.test(email3);
1030
+ }
1031
+ function formatBytes(bytes, decimals = 2) {
1032
+ if (bytes <= 0) return "0 B";
1033
+ const k = 1024;
1034
+ const sizes = ["B", "KB", "MB", "GB", "TB"];
1035
+ const i = Math.min(Math.max(Math.floor(Math.log(bytes) / Math.log(k)), 0), sizes.length - 1);
1036
+ return `${parseFloat((bytes / Math.pow(k, i)).toFixed(decimals))} ${sizes[i]}`;
1037
+ }
1038
+ function withTimeout(promise2, ms, message) {
1039
+ if (!ms) return promise2;
1040
+ return new Promise((resolve, reject) => {
1041
+ const timer = setTimeout(() => reject(new Error(message)), ms);
1042
+ promise2.then(
1043
+ (v) => {
1044
+ clearTimeout(timer);
1045
+ resolve(v);
1046
+ },
1047
+ (e) => {
1048
+ clearTimeout(timer);
1049
+ reject(e);
1050
+ }
1051
+ );
1052
+ });
1053
+ }
1054
+ function isValidEnvKey(key) {
1055
+ return /^[A-Za-z_][A-Za-z0-9_]*$/.test(key);
1056
+ }
1057
+ function wrapText(text, width = 88) {
1058
+ const out = [];
1059
+ for (const paragraph of text.split("\n")) {
1060
+ const words = paragraph.split(/\s+/).filter(Boolean);
1061
+ if (words.length === 0) {
1062
+ out.push("");
1063
+ continue;
1064
+ }
1065
+ let line = "";
1066
+ for (const word of words) {
1067
+ if (line && line.length + 1 + word.length > width) {
1068
+ out.push(line);
1069
+ line = word;
1070
+ } else {
1071
+ line = line ? `${line} ${word}` : word;
1072
+ }
1073
+ }
1074
+ if (line) out.push(line);
1075
+ }
1076
+ return out;
1077
+ }
1078
+
1020
1079
  // ../../packages/core/src/constants.ts
1021
1080
  var DEFAULT_IMAGE_REGISTRY = "ghcr.io/oblien";
1022
1081
 
@@ -1199,62 +1258,6 @@ var SYSTEM = {
1199
1258
  }
1200
1259
  };
1201
1260
 
1202
- // ../../packages/core/src/utils.ts
1203
- var REDIRECT_STATUSES = [301, 302, 307, 308];
1204
- var DEFAULT_REDIRECT_STATUS = 301;
1205
- function resolveRedirectStatus(status) {
1206
- return REDIRECT_STATUSES.includes(status) ? status : DEFAULT_REDIRECT_STATUS;
1207
- }
1208
- function isValidEmail(value) {
1209
- const email3 = value?.trim() ?? "";
1210
- if (!email3 || email3.length > 254) return false;
1211
- return /^[A-Za-z0-9._%+-]+@[A-Za-z0-9-]+(\.[A-Za-z0-9-]+)*\.[A-Za-z]{2,}$/.test(email3);
1212
- }
1213
- function formatBytes(bytes, decimals = 2) {
1214
- if (bytes <= 0) return "0 B";
1215
- const k = 1024;
1216
- const sizes = ["B", "KB", "MB", "GB", "TB"];
1217
- const i = Math.min(Math.max(Math.floor(Math.log(bytes) / Math.log(k)), 0), sizes.length - 1);
1218
- return `${parseFloat((bytes / Math.pow(k, i)).toFixed(decimals))} ${sizes[i]}`;
1219
- }
1220
- function withTimeout(promise2, ms, message) {
1221
- if (!ms) return promise2;
1222
- return new Promise((resolve, reject) => {
1223
- const timer = setTimeout(() => reject(new Error(message)), ms);
1224
- promise2.then(
1225
- (v) => {
1226
- clearTimeout(timer);
1227
- resolve(v);
1228
- },
1229
- (e) => {
1230
- clearTimeout(timer);
1231
- reject(e);
1232
- }
1233
- );
1234
- });
1235
- }
1236
- function wrapText(text, width = 88) {
1237
- const out = [];
1238
- for (const paragraph of text.split("\n")) {
1239
- const words = paragraph.split(/\s+/).filter(Boolean);
1240
- if (words.length === 0) {
1241
- out.push("");
1242
- continue;
1243
- }
1244
- let line = "";
1245
- for (const word of words) {
1246
- if (line && line.length + 1 + word.length > width) {
1247
- out.push(line);
1248
- line = word;
1249
- } else {
1250
- line = line ? `${line} ${word}` : word;
1251
- }
1252
- }
1253
- if (line) out.push(line);
1254
- }
1255
- return out;
1256
- }
1257
-
1258
1261
  // ../../packages/core/src/errors.ts
1259
1262
  var AppError = class extends Error {
1260
1263
  constructor(message, statusCode = 500, code) {
@@ -2548,6 +2551,104 @@ function hostFirewallRuleRemoval(firewall, cidrs, port) {
2548
2551
 
2549
2552
  // ../../packages/core/src/compose-spec.ts
2550
2553
  var asIs = (s) => s;
2554
+ var REMOTE_BUILD_CONTEXT_RE = /^(?:[A-Za-z][A-Za-z0-9+.-]*:\/\/|[^@\s]+@[^:\s]+:)/;
2555
+ var ABSOLUTE_BUILD_CONTEXT_RE = /^(?:[\\/]|~(?:[\\/]|$)|[A-Za-z]:[\\/]|\\\\)/;
2556
+ function composeOptionRequestsSomething(value) {
2557
+ if (value === void 0 || value === null || value === false || value === "" || value === 0) {
2558
+ return false;
2559
+ }
2560
+ if (Array.isArray(value)) return value.length > 0;
2561
+ if (typeof value === "object") return Object.keys(value).length > 0;
2562
+ return true;
2563
+ }
2564
+ function composeBuildIssues(build, options = {}) {
2565
+ if (build === void 0 || build === null) return [];
2566
+ const issues = [];
2567
+ const block = (field, reason) => issues.push({ field, reason, blocking: true });
2568
+ const validateContext = (context) => {
2569
+ const resolved = (options.interpolate?.(context) ?? context).trim();
2570
+ if (!resolved) {
2571
+ block("build.context", "build.context must not be empty.");
2572
+ } else if (REMOTE_BUILD_CONTEXT_RE.test(resolved)) {
2573
+ block(
2574
+ "build.context",
2575
+ "build.context uses a remote URL, which Openship cannot build as a repository-relative context."
2576
+ );
2577
+ } else if (!options.allowAbsoluteContext && ABSOLUTE_BUILD_CONTEXT_RE.test(resolved)) {
2578
+ block(
2579
+ "build.context",
2580
+ "build.context is absolute or home-relative; Openship requires a path inside the linked repository."
2581
+ );
2582
+ }
2583
+ };
2584
+ if (typeof build === "string") {
2585
+ validateContext(build);
2586
+ return issues;
2587
+ }
2588
+ if (typeof build !== "object" || Array.isArray(build)) {
2589
+ block("build", "build must be a context string or an object.");
2590
+ return issues;
2591
+ }
2592
+ const value = build;
2593
+ if (value.context !== void 0) {
2594
+ if (typeof value.context !== "string") {
2595
+ block("build.context", "build.context must be a string.");
2596
+ } else {
2597
+ validateContext(value.context);
2598
+ }
2599
+ }
2600
+ if (value.dockerfile !== void 0) {
2601
+ if (typeof value.dockerfile !== "string") {
2602
+ block("build.dockerfile", "build.dockerfile must be a string.");
2603
+ } else {
2604
+ const dockerfile = (options.interpolate?.(value.dockerfile) ?? value.dockerfile).trim();
2605
+ if (!dockerfile) {
2606
+ block("build.dockerfile", "build.dockerfile must not be empty.");
2607
+ } else if (REMOTE_BUILD_CONTEXT_RE.test(dockerfile) || ABSOLUTE_BUILD_CONTEXT_RE.test(dockerfile) || dockerfile.split(/[\\/]/).includes("..")) {
2608
+ block("build.dockerfile", "build.dockerfile must be a path inside its build context.");
2609
+ }
2610
+ }
2611
+ }
2612
+ const args = value.args;
2613
+ if (args !== void 0) {
2614
+ if (Array.isArray(args)) {
2615
+ args.forEach((entry, index) => {
2616
+ if (typeof entry !== "string") {
2617
+ block(`build.args[${index}]`, "build.args list entries must be strings.");
2618
+ return;
2619
+ }
2620
+ const resolvedEntry = options.interpolate?.(entry) ?? entry;
2621
+ const equals = resolvedEntry.indexOf("=");
2622
+ const key = (equals >= 0 ? resolvedEntry.slice(0, equals) : resolvedEntry).trim();
2623
+ if (!isValidEnvKey(key)) {
2624
+ block(`build.args[${index}]`, "build.args contains an invalid argument name.");
2625
+ }
2626
+ });
2627
+ } else if (args && typeof args === "object") {
2628
+ for (const [key, argValue] of Object.entries(args)) {
2629
+ if (!isValidEnvKey(key)) {
2630
+ block("build.args", "build.args contains an invalid argument name.");
2631
+ }
2632
+ if (argValue !== null && argValue !== void 0 && !["string", "number", "boolean"].includes(typeof argValue)) {
2633
+ block(`build.args.${key}`, "build.args values must be scalar or null.");
2634
+ }
2635
+ }
2636
+ } else {
2637
+ block("build.args", "build.args must be a list of strings or a scalar/null map.");
2638
+ }
2639
+ }
2640
+ const modeled = /* @__PURE__ */ new Set(["context", "dockerfile", "args"]);
2641
+ for (const [key, optionValue] of Object.entries(value)) {
2642
+ if (modeled.has(key) || /^x-/i.test(key) || !composeOptionRequestsSomething(optionValue)) {
2643
+ continue;
2644
+ }
2645
+ block(
2646
+ `build.${key}`,
2647
+ `build.${key} is not supported; ignoring it could change the produced image or omit requested build behavior.`
2648
+ );
2649
+ }
2650
+ return issues;
2651
+ }
2551
2652
  function composeMountToSpec(mount, interpolate = asIs) {
2552
2653
  const source = mount.source ?? mount.name;
2553
2654
  const target = mount.target;
@@ -6102,6 +6203,7 @@ COMMIT;
6102
6203
  {
6103
6204
  available: true,
6104
6205
  verified: true,
6206
+ minEngine: "0.6.6",
6105
6207
  id: "minio",
6106
6208
  name: "MinIO",
6107
6209
  description: "S3-compatible object storage with a web console \u2014 point any S3 client (aws-cli, SDKs) at it, or bind a bucket straight into another project's uploads.",
@@ -6596,6 +6698,7 @@ COMMIT;
6596
6698
  {
6597
6699
  available: true,
6598
6700
  verified: false,
6701
+ minEngine: "0.6.6",
6599
6702
  minResources: {
6600
6703
  memoryMb: 4096,
6601
6704
  cpuCores: 2
@@ -6777,6 +6880,7 @@ Two things worth knowing, both upstream behaviour: if the console shows a branch
6777
6880
  {
6778
6881
  available: true,
6779
6882
  verified: false,
6883
+ minEngine: "0.6.6",
6780
6884
  minResources: {
6781
6885
  memoryMb: 16384,
6782
6886
  cpuCores: 4
@@ -7599,6 +7703,7 @@ Two things worth knowing, both upstream behaviour: if the console shows a branch
7599
7703
  {
7600
7704
  available: true,
7601
7705
  verified: false,
7706
+ minEngine: "0.6.6",
7602
7707
  id: "redis",
7603
7708
  name: "Valkey (Redis)",
7604
7709
  description: "In-memory data store for caching, sessions, rate limits, and queues \u2014 Valkey, the open-source Redis fork. Drop-in Redis-compatible: point any redis client at it. Ships with RedisInsight, a browser UI that arrives already connected to this instance.",
@@ -8077,6 +8182,7 @@ Two things worth knowing, both upstream behaviour: if the console shows a branch
8077
8182
  {
8078
8183
  available: true,
8079
8184
  verified: false,
8185
+ minEngine: "0.6.6",
8080
8186
  id: "clickhouse",
8081
8187
  name: "ClickHouse",
8082
8188
  description: "The columnar SQL database for analytics \u2014 billions of rows scanned per second. Ships with the CH-UI console, so you get a SQL editor, schema browser and dashboards the moment it installs: sign in with the ClickHouse user and password below, no extra setup.",
@@ -8343,6 +8449,178 @@ Two things worth knowing, both upstream behaviour: if the console shows a branch
8343
8449
  ]
8344
8450
  }
8345
8451
  ]
8452
+ },
8453
+ {
8454
+ available: true,
8455
+ verified: true,
8456
+ id: "mindwire",
8457
+ name: "MindWire",
8458
+ description: "Run coding-agent runtimes behind a self-hosted control plane. MindWire keeps the runtime private and gives you one console to manage agents, credentials, and sessions.",
8459
+ repository: "https://github.com/oblien/mindwire",
8460
+ kind: "template",
8461
+ logo: "mindwire",
8462
+ category: "automation",
8463
+ tags: [
8464
+ "ai",
8465
+ "agents",
8466
+ "coding",
8467
+ "control-plane"
8468
+ ],
8469
+ framework: "docker-compose",
8470
+ services: [
8471
+ {
8472
+ name: "runtime",
8473
+ image: "ghcr.io/oblien/mindwire-runtime:0.1.3",
8474
+ environment: {
8475
+ ADDR: "0.0.0.0:8790",
8476
+ AGENT_TYPE: "claude-code",
8477
+ DAEMON_TOKEN: "{{config:MINDWIRE_RUNTIME_TOKEN}}"
8478
+ },
8479
+ secretEnv: [
8480
+ "DAEMON_TOKEN"
8481
+ ],
8482
+ volumes: [
8483
+ "mindwire_runtime:/home/node"
8484
+ ],
8485
+ restart: "unless-stopped"
8486
+ },
8487
+ {
8488
+ name: "console",
8489
+ image: "ghcr.io/oblien/mindwire-console:0.1.3",
8490
+ exposedPort: 8787,
8491
+ exposed: true,
8492
+ routes: [
8493
+ {
8494
+ port: 8787
8495
+ }
8496
+ ],
8497
+ dependsOn: [
8498
+ "runtime"
8499
+ ],
8500
+ environment: {
8501
+ NODE_ENV: "production",
8502
+ PORT: "8787",
8503
+ BASE_URL: "{{publicUrl:console}}",
8504
+ AUTH_DB_PATH: "/data/auth.db",
8505
+ CONSOLE_MODE: "self-hosted",
8506
+ CONSOLE_USERNAME: "{{config:CONSOLE_USERNAME}}",
8507
+ CONSOLE_PASSWORD: "{{config:CONSOLE_PASSWORD}}",
8508
+ AUTH_SECRET: "{{config:AUTH_SECRET}}",
8509
+ SECRETS_ENCRYPTION_KEY: "{{config:SECRETS_ENCRYPTION_KEY}}",
8510
+ ALLOW_LOCAL_RUNTIME: "true",
8511
+ ALLOW_SSH_RUNTIME: "true",
8512
+ ALLOW_DOCKER_RUNTIME: "true",
8513
+ ALLOW_REMOTE_RUNTIME: "true",
8514
+ DAEMON_URL: "http://runtime:8790",
8515
+ MINDWIRE_RUNTIME_TOKEN: "{{config:MINDWIRE_RUNTIME_TOKEN}}",
8516
+ SEED_DEFAULT_DAEMON: "true"
8517
+ },
8518
+ secretEnv: [
8519
+ "CONSOLE_PASSWORD",
8520
+ "AUTH_SECRET",
8521
+ "SECRETS_ENCRYPTION_KEY",
8522
+ "MINDWIRE_RUNTIME_TOKEN"
8523
+ ],
8524
+ volumes: [
8525
+ "mindwire_console:/data"
8526
+ ],
8527
+ healthcheck: {
8528
+ test: [
8529
+ "CMD-SHELL",
8530
+ `node -e "fetch('http://127.0.0.1:8787/api/ping').then(r => process.exit(r.ok ? 0 : 1)).catch(() => process.exit(1))"`
8531
+ ],
8532
+ interval: "10s",
8533
+ timeout: "5s",
8534
+ retries: 12,
8535
+ startPeriod: "20s"
8536
+ },
8537
+ restart: "unless-stopped"
8538
+ }
8539
+ ],
8540
+ configFields: [
8541
+ {
8542
+ key: "CONSOLE_USERNAME",
8543
+ service: "console",
8544
+ label: "Console username",
8545
+ help: "The single owner account for this self-hosted console.",
8546
+ default: "admin",
8547
+ required: true
8548
+ },
8549
+ {
8550
+ key: "CONSOLE_PASSWORD",
8551
+ service: "console",
8552
+ label: "Console password",
8553
+ help: "Auto-generated. Use this with the owner username to sign in.",
8554
+ type: "password",
8555
+ generate: "secret",
8556
+ secret: true
8557
+ },
8558
+ {
8559
+ key: "AUTH_SECRET",
8560
+ service: "console",
8561
+ label: "Session signing secret",
8562
+ help: "Auto-generated. Keep it stable so existing sign-in sessions remain valid after upgrades.",
8563
+ generate: "secret",
8564
+ secret: true
8565
+ },
8566
+ {
8567
+ key: "SECRETS_ENCRYPTION_KEY",
8568
+ service: "console",
8569
+ label: "Credential vault key",
8570
+ help: "Auto-generated 32-byte key used to encrypt provider and runtime credentials at rest.",
8571
+ generate: "secret",
8572
+ secret: true
8573
+ },
8574
+ {
8575
+ key: "MINDWIRE_RUNTIME_TOKEN",
8576
+ service: "runtime",
8577
+ label: "Runtime bearer token",
8578
+ help: "Auto-generated. Authenticates the Console to the private MindWire runtime.",
8579
+ generate: "secret",
8580
+ secret: true
8581
+ }
8582
+ ],
8583
+ management: {
8584
+ kind: "schema"
8585
+ },
8586
+ connection: {
8587
+ title: "Sign in to MindWire",
8588
+ description: "Open the Console and sign in with the generated owner credentials. The runtime stays private to this deployment.",
8589
+ guide: {
8590
+ intro: "MindWire gives you a production control plane for coding-agent runtimes without exposing the runtime API to the public internet.",
8591
+ defaultMode: "public"
8592
+ },
8593
+ outputs: [
8594
+ {
8595
+ id: "console",
8596
+ label: "MindWire Console",
8597
+ source: "publicUrl:console",
8598
+ kind: "url",
8599
+ recommended: true,
8600
+ help: "Open the control plane in your browser."
8601
+ },
8602
+ {
8603
+ id: "username",
8604
+ label: "Owner username",
8605
+ source: "env:console:CONSOLE_USERNAME"
8606
+ },
8607
+ {
8608
+ id: "password",
8609
+ label: "Owner password",
8610
+ source: "env:console:CONSOLE_PASSWORD",
8611
+ secret: true
8612
+ }
8613
+ ]
8614
+ },
8615
+ endpoints: [
8616
+ {
8617
+ service: "console",
8618
+ port: 8787,
8619
+ label: "MindWire Console",
8620
+ kind: "http",
8621
+ defaultMode: "domain"
8622
+ }
8623
+ ]
8346
8624
  }
8347
8625
  ]
8348
8626
  };
@@ -24641,6 +24919,240 @@ var ANNUAL_ENABLED = PRICING.annual.enabled;
24641
24919
  var ANNUAL_MONTHS_FREE = PRICING.annual.monthsFree;
24642
24920
  var FREE_DOMAIN_SUFFIX = PRICING.freeDomainSuffix;
24643
24921
 
24922
+ // ../../packages/core/src/project-source.ts
24923
+ var MAX_IMAGE_NAME_LENGTH = 255;
24924
+ var MAX_IMAGE_TAG_LENGTH = 128;
24925
+ var IMAGE_PATH_COMPONENT_RE = /^[a-z0-9]+(?:(?:[._]|__|-+)[a-z0-9]+)*$/;
24926
+ var IMAGE_TAG_RE = /^[A-Za-z0-9_][A-Za-z0-9_.-]{0,127}$/;
24927
+ var IMAGE_DIGEST_RE = /^([A-Za-z][A-Za-z0-9]*(?:[+._-][A-Za-z][A-Za-z0-9]*)*):([A-Za-z0-9=_-]+)$/;
24928
+ var IMAGE_DOMAIN_LABEL_RE = /^[A-Za-z0-9](?:[A-Za-z0-9-]*[A-Za-z0-9])?$/;
24929
+ function validateImageReference(ref) {
24930
+ if (typeof ref !== "string") return "Container image reference must be a string.";
24931
+ if (!ref) return "Container image reference cannot be empty.";
24932
+ if (ref !== ref.trim()) return "Container image reference cannot have surrounding whitespace.";
24933
+ if (/\s/.test(ref))
24934
+ return `Container image reference ${JSON.stringify(ref)} cannot contain whitespace.`;
24935
+ if (ref.includes("\0"))
24936
+ return `Container image reference ${JSON.stringify(ref)} contains a NUL byte.`;
24937
+ if (ref.includes("://")) {
24938
+ return `Container image reference ${JSON.stringify(ref)} must not include a URL scheme.`;
24939
+ }
24940
+ const at = ref.indexOf("@");
24941
+ if (at !== -1 && at !== ref.lastIndexOf("@")) {
24942
+ return `Container image reference ${JSON.stringify(ref)} contains more than one digest separator (@).`;
24943
+ }
24944
+ const nameAndTag = at === -1 ? ref : ref.slice(0, at);
24945
+ const digest = at === -1 ? null : ref.slice(at + 1);
24946
+ if (!nameAndTag)
24947
+ return `Container image reference ${JSON.stringify(ref)} is missing a repository name.`;
24948
+ if (digest !== null) {
24949
+ const parsed = IMAGE_DIGEST_RE.exec(digest);
24950
+ if (!parsed) {
24951
+ return `Container image reference ${JSON.stringify(ref)} has an invalid OCI digest.`;
24952
+ }
24953
+ if (parsed[1].toLowerCase() === "sha256" && !/^[a-fA-F0-9]{64}$/.test(parsed[2])) {
24954
+ return `Container image reference ${JSON.stringify(ref)} has an invalid sha256 digest (expected 64 hexadecimal characters).`;
24955
+ }
24956
+ }
24957
+ const lastSlash = nameAndTag.lastIndexOf("/");
24958
+ const lastColon = nameAndTag.lastIndexOf(":");
24959
+ const hasTag = lastColon > lastSlash;
24960
+ const name = hasTag ? nameAndTag.slice(0, lastColon) : nameAndTag;
24961
+ const tag = hasTag ? nameAndTag.slice(lastColon + 1) : null;
24962
+ if (!name)
24963
+ return `Container image reference ${JSON.stringify(ref)} is missing a repository name.`;
24964
+ if (name.length > MAX_IMAGE_NAME_LENGTH) {
24965
+ return `Container image repository name is too long (${name.length}; maximum ${MAX_IMAGE_NAME_LENGTH}).`;
24966
+ }
24967
+ if (tag !== null) {
24968
+ if (!tag) return `Container image reference ${JSON.stringify(ref)} has an empty tag.`;
24969
+ if (tag.length > MAX_IMAGE_TAG_LENGTH || !IMAGE_TAG_RE.test(tag)) {
24970
+ return `Container image reference ${JSON.stringify(ref)} has an invalid tag; tags must be 1-${MAX_IMAGE_TAG_LENGTH} ASCII letters, digits, underscores, periods, or hyphens and cannot start with a period or hyphen.`;
24971
+ }
24972
+ }
24973
+ const parts = name.split("/");
24974
+ if (parts.some((part) => !part)) {
24975
+ return `Container image reference ${JSON.stringify(ref)} contains an empty repository path component.`;
24976
+ }
24977
+ const first = parts[0];
24978
+ const hasRegistry = parts.length > 1 && (first.includes(".") || first.includes(":") || first === "localhost" || first.startsWith("["));
24979
+ const path = hasRegistry ? parts.slice(1) : parts;
24980
+ if (path.length === 0 || path.some((part) => !IMAGE_PATH_COMPONENT_RE.test(part))) {
24981
+ return `Container image reference ${JSON.stringify(ref)} has an invalid repository path; repository names must be lowercase OCI path components.`;
24982
+ }
24983
+ if (hasRegistry) {
24984
+ const registryError = validateImageRegistry(first);
24985
+ if (registryError) return `Container image reference ${JSON.stringify(ref)} ${registryError}`;
24986
+ }
24987
+ return null;
24988
+ }
24989
+ function validateReleaseVersionUrl(value) {
24990
+ if (typeof value !== "string" || !value.trim()) {
24991
+ return "Release version URL cannot be empty.";
24992
+ }
24993
+ if (value !== value.trim()) {
24994
+ return "Release version URL cannot have surrounding whitespace.";
24995
+ }
24996
+ let url2;
24997
+ try {
24998
+ url2 = new URL(value);
24999
+ } catch {
25000
+ return "Release version URL must be a valid HTTPS URL.";
25001
+ }
25002
+ if (url2.protocol !== "https:") {
25003
+ return "Release version URL must use HTTPS.";
25004
+ }
25005
+ if (url2.username || url2.password) {
25006
+ return "Release version URL must not include embedded credentials.";
25007
+ }
25008
+ return null;
25009
+ }
25010
+ function validateReleaseRepository(value) {
25011
+ if (typeof value !== "string" || !value.trim()) {
25012
+ return 'GitHub release repository must use the "owner/repository" format.';
25013
+ }
25014
+ if (value !== value.trim()) {
25015
+ return "GitHub release repository cannot have surrounding whitespace.";
25016
+ }
25017
+ const parts = value.split("/");
25018
+ if (parts.length !== 2) {
25019
+ return 'GitHub release repository must use the "owner/repository" format.';
25020
+ }
25021
+ const [owner, repository] = parts;
25022
+ const ownerValid = owner.length <= 39 && /^[A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?$/.test(owner);
25023
+ const repositoryValid = repository.length <= 100 && repository !== "." && repository !== ".." && /^[A-Za-z0-9_.-]+$/.test(repository);
25024
+ if (!ownerValid || !repositoryValid) {
25025
+ return "GitHub release repository contains an invalid owner or repository name.";
25026
+ }
25027
+ return null;
25028
+ }
25029
+ function isValidBracketedIpv6Host(host) {
25030
+ if (!/^\[[0-9a-fA-F:]+\]$/.test(host)) return false;
25031
+ try {
25032
+ const parsed = new URL(`http://${host}/`);
25033
+ return parsed.hostname.startsWith("[") && parsed.hostname.endsWith("]");
25034
+ } catch {
25035
+ return false;
25036
+ }
25037
+ }
25038
+ function validateImageRegistry(registry2) {
25039
+ let host = registry2;
25040
+ let port = null;
25041
+ if (registry2.startsWith("[")) {
25042
+ const close = registry2.indexOf("]");
25043
+ if (close <= 1) return "has an invalid bracketed IPv6 registry host.";
25044
+ host = registry2.slice(0, close + 1);
25045
+ const rest = registry2.slice(close + 1);
25046
+ if (rest) {
25047
+ if (!rest.startsWith(":")) return "has invalid characters after its registry host.";
25048
+ port = rest.slice(1);
25049
+ }
25050
+ if (!isValidBracketedIpv6Host(host)) {
25051
+ return "has an invalid bracketed IPv6 registry host.";
25052
+ }
25053
+ } else {
25054
+ const colon = registry2.lastIndexOf(":");
25055
+ if (colon !== -1) {
25056
+ host = registry2.slice(0, colon);
25057
+ port = registry2.slice(colon + 1);
25058
+ }
25059
+ if (!host) return "is missing its registry host.";
25060
+ const labels = host.split(".");
25061
+ if (labels.some((label) => !IMAGE_DOMAIN_LABEL_RE.test(label))) {
25062
+ return "has an invalid registry hostname.";
25063
+ }
25064
+ }
25065
+ if (port !== null) {
25066
+ if (!/^\d+$/.test(port)) return "has a non-numeric registry port.";
25067
+ const value = Number(port);
25068
+ if (value < 1 || value > 65535) return "has a registry port outside 1-65535.";
25069
+ }
25070
+ return null;
25071
+ }
25072
+ var RELEASE_IMAGE_PLACEHOLDERS = ["version", "tag"];
25073
+ function renderReleaseImage(template, opts) {
25074
+ if (typeof template !== "string" || !template.trim()) {
25075
+ throw new Error("Release image template cannot be empty.");
25076
+ }
25077
+ if (template !== template.trim()) {
25078
+ throw new Error("Release image template cannot have surrounding whitespace.");
25079
+ }
25080
+ let insidePlaceholder = false;
25081
+ for (const char of template) {
25082
+ if (char === "{") {
25083
+ if (insidePlaceholder) {
25084
+ throw new Error(
25085
+ `Release image template ${JSON.stringify(template)} contains malformed placeholder braces.`
25086
+ );
25087
+ }
25088
+ insidePlaceholder = true;
25089
+ } else if (char === "}") {
25090
+ if (!insidePlaceholder) {
25091
+ throw new Error(
25092
+ `Release image template ${JSON.stringify(template)} contains malformed placeholder braces.`
25093
+ );
25094
+ }
25095
+ insidePlaceholder = false;
25096
+ }
25097
+ }
25098
+ if (insidePlaceholder) {
25099
+ throw new Error(
25100
+ `Release image template ${JSON.stringify(template)} contains malformed placeholder braces.`
25101
+ );
25102
+ }
25103
+ const placeholders2 = template.match(/\{[^{}]*\}/g) ?? [];
25104
+ const unknown2 = placeholders2.filter(
25105
+ (placeholder) => !RELEASE_IMAGE_PLACEHOLDERS.includes(placeholder.slice(1, -1))
25106
+ );
25107
+ if (unknown2.length > 0) {
25108
+ throw new Error(
25109
+ `Release image template ${JSON.stringify(template)} uses unknown placeholder(s) ${unknown2.join(", ")}. Supported: ${RELEASE_IMAGE_PLACEHOLDERS.map((p) => `{${p}}`).join(" ")}.`
25110
+ );
25111
+ }
25112
+ if (!template.includes("{version}") && !template.includes("{tag}")) {
25113
+ throw new Error(
25114
+ `Release image template ${JSON.stringify(template)} must contain {version} or {tag}.`
25115
+ );
25116
+ }
25117
+ if (template.includes("@")) {
25118
+ throw new Error(
25119
+ `Release image template ${JSON.stringify(template)} must use a tag, not a digest.`
25120
+ );
25121
+ }
25122
+ const lastSlash = template.lastIndexOf("/");
25123
+ const tagColon = template.lastIndexOf(":");
25124
+ if (tagColon <= lastSlash) {
25125
+ throw new Error(
25126
+ `Release image template ${JSON.stringify(template)} must put {version} or {tag} in an explicit image tag.`
25127
+ );
25128
+ }
25129
+ const tagStart = tagColon + 1;
25130
+ for (const placeholder of ["{version}", "{tag}"]) {
25131
+ let at = template.indexOf(placeholder);
25132
+ while (at !== -1) {
25133
+ if (at < tagStart) {
25134
+ throw new Error(
25135
+ `Release image template ${JSON.stringify(template)} may use ${placeholder} only in the image tag.`
25136
+ );
25137
+ }
25138
+ at = template.indexOf(placeholder, at + placeholder.length);
25139
+ }
25140
+ }
25141
+ const rawVersion = opts.version?.trim();
25142
+ const rawTag = opts.tag?.trim();
25143
+ if (!rawVersion) throw new Error("Release image version cannot be empty.");
25144
+ if (!rawTag) throw new Error("Release image tag cannot be empty.");
25145
+ const version2 = rawVersion.replace(/^v/i, "");
25146
+ if (!version2) throw new Error("Release image version cannot contain only a leading v.");
25147
+ const rendered = template.replace(
25148
+ /\{(?:version|tag)\}/g,
25149
+ (placeholder) => placeholder === "{version}" ? version2 : rawTag
25150
+ );
25151
+ const invalid = validateImageReference(rendered);
25152
+ if (invalid) throw new Error(invalid);
25153
+ return rendered;
25154
+ }
25155
+
24644
25156
  // ../../packages/core/src/updates/types.ts
24645
25157
  var GITHUB_REPO = "oblien/openship";
24646
25158
  var RELEASES_LATEST_API = `https://api.github.com/repos/${GITHUB_REPO}/releases/latest`;
@@ -24772,6 +25284,13 @@ var AUDIT_EVENTS = {
24772
25284
  tone: "warning",
24773
25285
  description: "A delete was refused \u2014 something still depends on this app."
24774
25286
  },
25287
+ "project.deletion.failed": {
25288
+ category: "apps",
25289
+ action: "failed to delete",
25290
+ label: "App deletion failed",
25291
+ tone: "warning",
25292
+ description: "Cleanup did not complete, so the app record was kept for a safe retry."
25293
+ },
24775
25294
  "project:write": {
24776
25295
  category: "apps",
24777
25296
  action: "changed",
@@ -25259,6 +25778,20 @@ var AUDIT_EVENTS = {
25259
25778
  tone: "warning",
25260
25779
  description: "A full database dump left this instance."
25261
25780
  },
25781
+ "instance.data.receive_code_created": {
25782
+ category: "security",
25783
+ action: "created a direct data-transfer receive code",
25784
+ label: "Data-transfer receive code created",
25785
+ tone: "warning",
25786
+ description: "A short-lived code was created to receive instance data directly."
25787
+ },
25788
+ "instance.data.sent": {
25789
+ category: "security",
25790
+ action: "sent instance data to another instance",
25791
+ label: "Instance data sent",
25792
+ tone: "warning",
25793
+ description: "Selected instance data was transferred directly to another instance."
25794
+ },
25262
25795
  "instance.data.imported": {
25263
25796
  category: "security",
25264
25797
  action: "imported instance data",
@@ -25767,6 +26300,60 @@ function idSuffix(distroId) {
25767
26300
  return distroId ? ` (os-release ID=${JSON.stringify(distroId)})` : "";
25768
26301
  }
25769
26302
 
26303
+ // ../../packages/core/src/network.ts
26304
+ function ipv4Octets(host) {
26305
+ const parts = host.split(".");
26306
+ if (parts.length !== 4) return null;
26307
+ const octets = parts.map((part) => /^(?:0|[1-9]\d{0,2})$/.test(part) ? Number(part) : NaN);
26308
+ if (octets.some((octet) => !Number.isInteger(octet) || octet < 0 || octet > 255)) {
26309
+ return null;
26310
+ }
26311
+ return octets;
26312
+ }
26313
+ function ipv6Words(host) {
26314
+ let value = host;
26315
+ const zone = value.indexOf("%");
26316
+ if (zone >= 0) value = value.slice(0, zone);
26317
+ const dottedTail = /(?:^|:)(\d+\.\d+\.\d+\.\d+)$/.exec(value)?.[1];
26318
+ if (dottedTail) {
26319
+ const octets = ipv4Octets(dottedTail);
26320
+ if (!octets) return null;
26321
+ const replacement = `${(octets[0] << 8 | octets[1]).toString(16)}:${(octets[2] << 8 | octets[3]).toString(16)}`;
26322
+ value = value.slice(0, -dottedTail.length) + replacement;
26323
+ }
26324
+ if ((value.match(/::/g) ?? []).length > 1) return null;
26325
+ const [leftRaw, rightRaw] = value.split("::");
26326
+ const left = leftRaw ? leftRaw.split(":") : [];
26327
+ const right = rightRaw ? rightRaw.split(":") : [];
26328
+ if ([...left, ...right].some((word) => !/^[0-9a-f]{1,4}$/i.test(word))) return null;
26329
+ const compressed = value.includes("::");
26330
+ const missing = 8 - left.length - right.length;
26331
+ if (!compressed && missing !== 0 || compressed && missing < 1) return null;
26332
+ return [...left, ...Array.from({ length: missing }, () => "0"), ...right].map(
26333
+ (word) => Number.parseInt(word, 16)
26334
+ );
26335
+ }
26336
+ function isLoopbackHost(host) {
26337
+ if (!host) return false;
26338
+ let value = host.trim().toLowerCase();
26339
+ if (value.startsWith("[") && value.endsWith("]")) value = value.slice(1, -1);
26340
+ value = value.replace(/\.+$/, "");
26341
+ if (!value) return false;
26342
+ if (value === "localhost" || value.endsWith(".localhost") || value === "localhost.localdomain" || value === "ip6-localhost" || value === "ip6-loopback") {
26343
+ return true;
26344
+ }
26345
+ const ipv43 = ipv4Octets(value);
26346
+ if (ipv43) return ipv43[0] === 127 || ipv43.every((octet) => octet === 0);
26347
+ const words = ipv6Words(value);
26348
+ if (!words) return false;
26349
+ if (words.every((word) => word === 0)) return true;
26350
+ if (words.slice(0, 7).every((word) => word === 0) && words[7] === 1) return true;
26351
+ const ipv4PrefixIsLocal = words[6] >> 8 === 127;
26352
+ const mapped = words.slice(0, 5).every((word) => word === 0) && words[5] === 65535;
26353
+ const compatible = words.slice(0, 6).every((word) => word === 0);
26354
+ return ipv4PrefixIsLocal && (mapped || compatible);
26355
+ }
26356
+
25770
26357
  export {
25771
26358
  shellQuote,
25772
26359
  shellSplitWords,
@@ -25782,6 +26369,13 @@ export {
25782
26369
  appVolumeTargets,
25783
26370
  parseComposeNamespace,
25784
26371
  ownsNetworkEndpoint,
26372
+ resolveRedirectStatus,
26373
+ isValidEmail,
26374
+ formatBytes,
26375
+ withTimeout,
26376
+ isValidEnvKey,
26377
+ wrapText,
26378
+ composeBuildIssues,
25785
26379
  composeMountToSpec,
25786
26380
  composeMountIssues,
25787
26381
  composePortToSpec,
@@ -25792,11 +26386,6 @@ export {
25792
26386
  buildEdgeImageRef,
25793
26387
  buildMailImageRef,
25794
26388
  SYSTEM,
25795
- resolveRedirectStatus,
25796
- isValidEmail,
25797
- formatBytes,
25798
- withTimeout,
25799
- wrapText,
25800
26389
  AppError,
25801
26390
  DeployError,
25802
26391
  safeErrorMessage,
@@ -25816,6 +26405,9 @@ export {
25816
26405
  missingOutputDirectoryMessage,
25817
26406
  compareSemver,
25818
26407
  PRICING,
26408
+ validateReleaseVersionUrl,
26409
+ validateReleaseRepository,
26410
+ renderReleaseImage,
25819
26411
  resolveCliUpdatePlan,
25820
26412
  cliInstallCommand,
25821
26413
  PROXY_DIRECTIVES,
@@ -25850,5 +26442,6 @@ export {
25850
26442
  summarizeHostChannelCause,
25851
26443
  hostChannelRuleScope,
25852
26444
  hostFirewallRule,
25853
- hostFirewallRuleRemoval
26445
+ hostFirewallRuleRemoval,
26446
+ isLoopbackHost
25854
26447
  };