@ima-jin/config 0.7.0 → 0.8.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.
package/src/services.ts CHANGED
@@ -62,6 +62,9 @@ export const SERVICES: readonly ServiceDefinition[] = [
62
62
  { name: 'github', label: 'GitHub', icon: '🐙', description: 'Source code', devPort: 3000, prodPort: 7000, schema: "public", tier: 'core', visibility: 'public', category: 'meta', externalUrl: 'https://github.com/ima-jin/imajin-ai' },
63
63
  { name: 'docs', label: 'Docs', icon: '📄', description: 'API documentation', devPort: 3000, prodPort: 7000, schema: "public", tier: 'core', visibility: 'public', category: 'meta', wwwPath: '/developer-guide' },
64
64
 
65
+ // Daemon processes — not web apps, no HTTP port exposed
66
+ { name: 'broker-agent', label: 'Broker Agent', icon: '🤖', description: 'Telegram broker agent — conversational surface for social coordination (#1101)', devPort: 0, prodPort: 0, schema: null, tier: 'core', visibility: 'internal', category: 'infrastructure' },
67
+
65
68
  // Connected apps (separate repos) will use the plugin architecture (#249).
66
69
  // Not included here — they authenticate via delegated sessions, not the monorepo manifest.
67
70
  ] as const satisfies readonly ServiceDefinition[];
@@ -76,7 +79,8 @@ export function getService(name: string): ServiceDefinition | undefined {
76
79
  /** Get the port for a service in a given environment */
77
80
  export function getPort(name: string, env: "dev" | "prod" = "dev"): number | undefined {
78
81
  const svc = getService(name);
79
- return svc ? (env === "prod" ? svc.prodPort : svc.devPort) : undefined;
82
+ if (!svc) return undefined;
83
+ return env === "prod" ? svc.prodPort : svc.devPort;
80
84
  }
81
85
 
82
86
  /** Get internal URL for a service (server-side calls) */
@@ -115,7 +119,7 @@ export function getPublicUrl(
115
119
 
116
120
  // Single-domain mode: prefix contains dots (e.g. "dev-jin.imajin.ai/")
117
121
  // Build https://{prefix}{name} instead of https://{prefix}-{name}.{domain}
118
- if (prefix && prefix.includes(".")) {
122
+ if (prefix?.includes(".")) {
119
123
  const base = prefix.endsWith("/") ? prefix.slice(0, -1) : prefix;
120
124
  // "www" or "kernel" = root of the node, no suffix
121
125
  if (name === "www" || name === "kernel") return `https://${base}`;
@@ -147,8 +151,8 @@ export function buildPublicUrl(
147
151
  if (explicit) return explicit;
148
152
  }
149
153
 
150
- const p = servicePrefix ?? (typeof process !== "undefined" ? process.env.NEXT_PUBLIC_SERVICE_PREFIX : undefined) ?? "https://";
151
- const d = domain ?? (typeof process !== "undefined" ? process.env.NEXT_PUBLIC_DOMAIN : undefined) ?? "imajin.ai";
154
+ const p = servicePrefix ?? (typeof process === "undefined" ? undefined : process.env.NEXT_PUBLIC_SERVICE_PREFIX) ?? "https://";
155
+ const d = domain ?? (typeof process === "undefined" ? undefined : process.env.NEXT_PUBLIC_DOMAIN) ?? "imajin.ai";
152
156
 
153
157
  // Localhost detection
154
158
  if (p.includes("localhost") || d.includes("localhost")) {
@@ -168,6 +172,45 @@ export function buildPublicUrl(
168
172
  return getPublicUrl(name, { prefix: match, domain: d });
169
173
  }
170
174
 
175
+ /**
176
+ * Build an ABSOLUTE public URL for a service — for use OUTSIDE the browser
177
+ * origin: email links, Stripe/webhook return/success URLs, externally shared
178
+ * links, and cross-node links. Unlike buildPublicUrl(), this never returns a
179
+ * relative path.
180
+ *
181
+ * Resolution order:
182
+ * 1. explicit NEXT_PUBLIC_{NAME}_URL env var (only if already absolute)
183
+ * 2. localhost dev → http://localhost:{devPort} from the canonical port map
184
+ * 3. otherwise an absolute https URL derived from NEXT_PUBLIC_SERVICE_PREFIX
185
+ * + NEXT_PUBLIC_DOMAIN via getPublicUrl()
186
+ *
187
+ * @example
188
+ * buildPublicUrlAbsolute('chat') → https://jin.imajin.ai/chat (single-domain)
189
+ * buildPublicUrlAbsolute('kernel') → https://jin.imajin.ai (node root)
190
+ */
191
+ export function buildPublicUrlAbsolute(name: string): string {
192
+ // 1. Explicit per-service override wins if it is already absolute.
193
+ if (typeof process !== "undefined") {
194
+ const explicit = process.env[`NEXT_PUBLIC_${name.toUpperCase()}_URL`];
195
+ if (explicit && /^https?:\/\//.test(explicit)) return explicit;
196
+ }
197
+
198
+ const prefix =
199
+ (typeof process === "undefined" ? undefined : process.env.NEXT_PUBLIC_SERVICE_PREFIX) ?? "https://";
200
+ const domain =
201
+ (typeof process === "undefined" ? undefined : process.env.NEXT_PUBLIC_DOMAIN) ?? "imajin.ai";
202
+
203
+ // 2. Localhost dev → canonical dev port.
204
+ if (prefix.includes("localhost") || domain.includes("localhost")) {
205
+ const port = getPort(name, "dev");
206
+ return port ? `http://localhost:${port}` : "http://localhost:3000";
207
+ }
208
+
209
+ // 3. Force absolute construction (getPublicUrl never returns a relative path).
210
+ const match = prefix.replace(/^https?:\/\//, "").replace(/-$/, "") || undefined;
211
+ return getPublicUrl(name, { prefix: match, domain });
212
+ }
213
+
171
214
  /** All service names */
172
215
  export const SERVICE_NAMES = SERVICES.map((s) => s.name);
173
216