@lobu/cli 3.0.3 → 3.0.4

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 (106) hide show
  1. package/README.md +8 -8
  2. package/dist/__tests__/login.test.d.ts +2 -0
  3. package/dist/__tests__/login.test.d.ts.map +1 -0
  4. package/dist/__tests__/login.test.js +173 -0
  5. package/dist/__tests__/login.test.js.map +1 -0
  6. package/dist/api/client.d.ts.map +1 -1
  7. package/dist/api/client.js +3 -5
  8. package/dist/api/client.js.map +1 -1
  9. package/dist/api/context.d.ts +22 -0
  10. package/dist/api/context.d.ts.map +1 -0
  11. package/dist/api/context.js +113 -0
  12. package/dist/api/context.js.map +1 -0
  13. package/dist/api/credentials.d.ts +9 -4
  14. package/dist/api/credentials.d.ts.map +1 -1
  15. package/dist/api/credentials.js +127 -15
  16. package/dist/api/credentials.js.map +1 -1
  17. package/dist/commands/chat.d.ts +11 -0
  18. package/dist/commands/chat.d.ts.map +1 -0
  19. package/dist/commands/chat.js +195 -0
  20. package/dist/commands/chat.js.map +1 -0
  21. package/dist/commands/context.d.ts +8 -0
  22. package/dist/commands/context.d.ts.map +1 -0
  23. package/dist/commands/context.js +46 -0
  24. package/dist/commands/context.js.map +1 -0
  25. package/dist/commands/dev.d.ts +1 -8
  26. package/dist/commands/dev.d.ts.map +1 -1
  27. package/dist/commands/dev.js +236 -57
  28. package/dist/commands/dev.js.map +1 -1
  29. package/dist/commands/init.d.ts.map +1 -1
  30. package/dist/commands/init.js +351 -676
  31. package/dist/commands/init.js.map +1 -1
  32. package/dist/commands/launch.d.ts.map +1 -1
  33. package/dist/commands/launch.js +2 -8
  34. package/dist/commands/launch.js.map +1 -1
  35. package/dist/commands/login.d.ts +2 -0
  36. package/dist/commands/login.d.ts.map +1 -1
  37. package/dist/commands/login.js +283 -14
  38. package/dist/commands/login.js.map +1 -1
  39. package/dist/commands/logout.d.ts +3 -1
  40. package/dist/commands/logout.d.ts.map +1 -1
  41. package/dist/commands/logout.js +5 -3
  42. package/dist/commands/logout.js.map +1 -1
  43. package/dist/commands/providers/add.d.ts.map +1 -1
  44. package/dist/commands/providers/add.js +38 -14
  45. package/dist/commands/providers/add.js.map +1 -1
  46. package/dist/commands/providers/list.d.ts.map +1 -1
  47. package/dist/commands/providers/list.js +4 -2
  48. package/dist/commands/providers/list.js.map +1 -1
  49. package/dist/commands/skills/add.d.ts.map +1 -1
  50. package/dist/commands/skills/add.js +25 -7
  51. package/dist/commands/skills/add.js.map +1 -1
  52. package/dist/commands/skills/info.d.ts.map +1 -1
  53. package/dist/commands/skills/info.js.map +1 -1
  54. package/dist/commands/skills/list.d.ts.map +1 -1
  55. package/dist/commands/skills/list.js.map +1 -1
  56. package/dist/commands/skills/registry.d.ts +5 -0
  57. package/dist/commands/skills/registry.d.ts.map +1 -1
  58. package/dist/commands/skills/registry.js.map +1 -1
  59. package/dist/commands/skills/search.d.ts.map +1 -1
  60. package/dist/commands/skills/search.js +3 -1
  61. package/dist/commands/skills/search.js.map +1 -1
  62. package/dist/commands/status.d.ts +1 -1
  63. package/dist/commands/status.d.ts.map +1 -1
  64. package/dist/commands/status.js +107 -4
  65. package/dist/commands/status.js.map +1 -1
  66. package/dist/commands/validate.d.ts.map +1 -1
  67. package/dist/commands/validate.js +9 -20
  68. package/dist/commands/validate.js.map +1 -1
  69. package/dist/commands/whoami.d.ts +3 -1
  70. package/dist/commands/whoami.d.ts.map +1 -1
  71. package/dist/commands/whoami.js +17 -3
  72. package/dist/commands/whoami.js.map +1 -1
  73. package/dist/config/agents-manifest.d.ts +92 -0
  74. package/dist/config/agents-manifest.d.ts.map +1 -0
  75. package/dist/config/agents-manifest.js +7 -0
  76. package/dist/config/agents-manifest.js.map +1 -0
  77. package/dist/config/loader.d.ts +18 -0
  78. package/dist/config/loader.d.ts.map +1 -1
  79. package/dist/config/loader.js +62 -2
  80. package/dist/config/loader.js.map +1 -1
  81. package/dist/config/platform-schemas.d.ts +120 -0
  82. package/dist/config/platform-schemas.d.ts.map +1 -0
  83. package/dist/config/platform-schemas.js +97 -0
  84. package/dist/config/platform-schemas.js.map +1 -0
  85. package/dist/config/schema.d.ts +546 -111
  86. package/dist/config/schema.d.ts.map +1 -1
  87. package/dist/config/schema.js +26 -19
  88. package/dist/config/schema.js.map +1 -1
  89. package/dist/config/transformer.d.ts +3 -0
  90. package/dist/config/transformer.d.ts.map +1 -1
  91. package/dist/config/transformer.js +7 -10
  92. package/dist/config/transformer.js.map +1 -1
  93. package/dist/index.d.ts +0 -4
  94. package/dist/index.d.ts.map +1 -1
  95. package/dist/index.js +52 -10
  96. package/dist/index.js.map +1 -1
  97. package/dist/system-skills.json +89 -29
  98. package/dist/templates/.env.tmpl +3 -14
  99. package/dist/templates/.gitignore.tmpl +1 -0
  100. package/dist/templates/README.md.tmpl +7 -8
  101. package/dist/utils/markdown.d.ts +3 -0
  102. package/dist/utils/markdown.d.ts.map +1 -0
  103. package/dist/utils/markdown.js +10 -0
  104. package/dist/utils/markdown.js.map +1 -0
  105. package/package.json +4 -2
  106. package/dist/templates/lobu.toml.tmpl +0 -44
@@ -0,0 +1,11 @@
1
+ /**
2
+ * `lobu chat "prompt"` — send a prompt to an agent and stream the response.
3
+ *
4
+ * Requires `lobu dev` running. Connects to the local gateway,
5
+ * creates a session, sends the message, streams output, then exits.
6
+ */
7
+ export declare function chatCommand(cwd: string, prompt: string, options: {
8
+ agent?: string;
9
+ gateway?: string;
10
+ }): Promise<void>;
11
+ //# sourceMappingURL=chat.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chat.d.ts","sourceRoot":"","sources":["../../src/commands/chat.ts"],"names":[],"mappings":"AAOA;;;;;GAKG;AACH,wBAAsB,WAAW,CAC/B,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,MAAM,EACd,OAAO,EAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,GAC5C,OAAO,CAAC,IAAI,CAAC,CAgFf"}
@@ -0,0 +1,195 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+ import chalk from "chalk";
4
+ import { getToken } from "../api/credentials.js";
5
+ import { isLoadError, loadConfig } from "../config/loader.js";
6
+ import { renderMarkdown } from "../utils/markdown.js";
7
+ /**
8
+ * `lobu chat "prompt"` — send a prompt to an agent and stream the response.
9
+ *
10
+ * Requires `lobu dev` running. Connects to the local gateway,
11
+ * creates a session, sends the message, streams output, then exits.
12
+ */
13
+ export async function chatCommand(cwd, prompt, options) {
14
+ const gatewayUrl = (options.gateway ?? (await resolveGatewayUrl(cwd))).replace(/\/$/, "");
15
+ // Resolve auth token: CLI JWT (from `lobu login`) → ADMIN_PASSWORD env var
16
+ const authToken = (await getToken()) ?? process.env.ADMIN_PASSWORD;
17
+ if (!authToken) {
18
+ console.error(chalk.red("\n Authentication required. Run `lobu login` or set ADMIN_PASSWORD.\n"));
19
+ process.exit(1);
20
+ }
21
+ // Resolve agent ID from flag or first agent in lobu.toml
22
+ const agentId = options.agent ?? (await resolveAgentId(cwd));
23
+ // 1. Create agent session
24
+ const createRes = await fetch(`${gatewayUrl}/api/v1/agents`, {
25
+ method: "POST",
26
+ headers: {
27
+ "Content-Type": "application/json",
28
+ Authorization: `Bearer ${authToken}`,
29
+ },
30
+ body: JSON.stringify({ agentId }),
31
+ });
32
+ if (!createRes.ok) {
33
+ const body = await createRes.text().catch(() => "");
34
+ if (createRes.status === 401) {
35
+ console.error(chalk.red("\n Authentication required. Run `lobu login` or set ADMIN_PASSWORD.\n"));
36
+ process.exit(1);
37
+ }
38
+ console.error(chalk.red(`\n Failed to create session (${createRes.status}): ${body}\n`));
39
+ process.exit(1);
40
+ }
41
+ const session = (await createRes.json());
42
+ // Build URLs from gateway flag (returned URLs may point to a public domain)
43
+ const base = `${gatewayUrl}/api/v1/agents/${session.agentId}`;
44
+ const sseUrl = `${base}/events`;
45
+ const messagesUrl = `${base}/messages`;
46
+ // 2. Open SSE connection before sending message so we don't miss events
47
+ const sseController = new AbortController();
48
+ const streaming = streamResponse(sseUrl, session.token, sseController);
49
+ // 3. Send the prompt
50
+ const msgRes = await fetch(messagesUrl, {
51
+ method: "POST",
52
+ headers: {
53
+ "Content-Type": "application/json",
54
+ Authorization: `Bearer ${session.token}`,
55
+ },
56
+ body: JSON.stringify({ content: prompt }),
57
+ });
58
+ if (!msgRes.ok) {
59
+ sseController.abort();
60
+ const body = await msgRes.text().catch(() => "");
61
+ console.error(chalk.red(`\n Failed to send message (${msgRes.status}): ${body}\n`));
62
+ process.exit(1);
63
+ }
64
+ // 4. Wait for streaming to complete
65
+ await streaming;
66
+ }
67
+ async function resolveAgentId(cwd) {
68
+ const result = await loadConfig(cwd);
69
+ if (isLoadError(result)) {
70
+ console.error(chalk.red(`\n ${result.error}\n`));
71
+ return process.exit(1);
72
+ }
73
+ const ids = Object.keys(result.config.agents);
74
+ if (ids.length === 0) {
75
+ console.error(chalk.red("\n No agents found in lobu.toml\n"));
76
+ return process.exit(1);
77
+ }
78
+ return ids[0];
79
+ }
80
+ /**
81
+ * Connect to SSE, print deltas to stdout, resolve on complete/error.
82
+ */
83
+ async function streamResponse(sseUrl, token, controller) {
84
+ const OVERALL_TIMEOUT_MS = 5 * 60 * 1000;
85
+ const IDLE_TIMEOUT_MS = 60 * 1000;
86
+ const overallTimer = setTimeout(() => controller.abort(), OVERALL_TIMEOUT_MS);
87
+ let idleTimer = setTimeout(() => controller.abort(), IDLE_TIMEOUT_MS);
88
+ const resetIdleTimer = () => {
89
+ clearTimeout(idleTimer);
90
+ idleTimer = setTimeout(() => controller.abort(), IDLE_TIMEOUT_MS);
91
+ };
92
+ try {
93
+ const res = await fetch(sseUrl, {
94
+ headers: { Authorization: `Bearer ${token}` },
95
+ signal: controller.signal,
96
+ });
97
+ if (!res.ok || !res.body) {
98
+ console.error(chalk.red(`\n SSE connection failed (${res.status})\n`));
99
+ process.exit(1);
100
+ }
101
+ const reader = res.body.getReader();
102
+ const decoder = new TextDecoder();
103
+ let buffer = "";
104
+ let currentEvent = "";
105
+ while (true) {
106
+ const { done, value } = await reader.read();
107
+ if (done)
108
+ break;
109
+ resetIdleTimer();
110
+ buffer += decoder.decode(value, { stream: true });
111
+ const lines = buffer.split("\n");
112
+ buffer = lines.pop() ?? "";
113
+ for (const line of lines) {
114
+ if (line.startsWith("event: ")) {
115
+ currentEvent = line.slice(7).trim();
116
+ }
117
+ else if (line.startsWith("data: ") && currentEvent) {
118
+ const data = parseJSON(line.slice(6));
119
+ if (!data)
120
+ continue;
121
+ switch (currentEvent) {
122
+ case "output":
123
+ if (typeof data.content === "string")
124
+ process.stdout.write(renderMarkdown(data.content));
125
+ break;
126
+ case "ephemeral":
127
+ if (typeof data.content === "string") {
128
+ console.error(`\n${renderMarkdown(data.content)}\n`);
129
+ }
130
+ controller.abort();
131
+ process.exit(1);
132
+ break;
133
+ case "complete":
134
+ process.stdout.write("\n");
135
+ controller.abort();
136
+ return;
137
+ case "error":
138
+ process.stdout.write("\n");
139
+ console.error(chalk.red(`\n Agent error: ${String(data.error)}\n`));
140
+ controller.abort();
141
+ process.exit(1);
142
+ }
143
+ currentEvent = "";
144
+ }
145
+ else if (line === "") {
146
+ currentEvent = "";
147
+ }
148
+ }
149
+ }
150
+ }
151
+ catch (err) {
152
+ // AbortError is expected when we call controller.abort() on complete
153
+ if (err instanceof Error && err.name === "AbortError")
154
+ return;
155
+ throw err;
156
+ }
157
+ finally {
158
+ clearTimeout(overallTimer);
159
+ clearTimeout(idleTimer);
160
+ }
161
+ }
162
+ function parseJSON(str) {
163
+ try {
164
+ const parsed = JSON.parse(str);
165
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
166
+ return parsed;
167
+ }
168
+ return null;
169
+ }
170
+ catch {
171
+ return null;
172
+ }
173
+ }
174
+ async function resolveGatewayUrl(cwd) {
175
+ try {
176
+ const envContent = await readFile(join(cwd, ".env"), "utf-8");
177
+ for (const line of envContent.split("\n")) {
178
+ const trimmed = line.trim();
179
+ if (trimmed.startsWith("GATEWAY_PORT=")) {
180
+ let port = trimmed.slice("GATEWAY_PORT=".length);
181
+ if ((port.startsWith('"') && port.endsWith('"')) ||
182
+ (port.startsWith("'") && port.endsWith("'"))) {
183
+ port = port.slice(1, -1);
184
+ }
185
+ if (port)
186
+ return `http://localhost:${port}`;
187
+ }
188
+ }
189
+ }
190
+ catch {
191
+ // No .env file
192
+ }
193
+ return "http://localhost:8080";
194
+ }
195
+ //# sourceMappingURL=chat.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chat.js","sourceRoot":"","sources":["../../src/commands/chat.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ export declare function contextListCommand(): Promise<void>;
2
+ export declare function contextCurrentCommand(): Promise<void>;
3
+ export declare function contextAddCommand(options: {
4
+ name: string;
5
+ apiUrl: string;
6
+ }): Promise<void>;
7
+ export declare function contextUseCommand(name: string): Promise<void>;
8
+ //# sourceMappingURL=context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../src/commands/context.ts"],"names":[],"mappings":"AASA,wBAAsB,kBAAkB,IAAI,OAAO,CAAC,IAAI,CAAC,CAqBxD;AAED,wBAAsB,qBAAqB,IAAI,OAAO,CAAC,IAAI,CAAC,CAU3D;AAED,wBAAsB,iBAAiB,CAAC,OAAO,EAAE;IAC/C,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;CAChB,GAAG,OAAO,CAAC,IAAI,CAAC,CAKhB;AAED,wBAAsB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAWnE"}
@@ -0,0 +1,46 @@
1
+ import chalk from "chalk";
2
+ import { addContext, getCurrentContextName, loadContextConfig, resolveContext, setCurrentContext, } from "../api/context.js";
3
+ export async function contextListCommand() {
4
+ const config = await loadContextConfig();
5
+ const currentContext = await getCurrentContextName();
6
+ console.log(chalk.bold("\n Lobu contexts"));
7
+ for (const [name, context] of Object.entries(config.contexts)) {
8
+ const marker = name === currentContext ? chalk.green(" *") : " ";
9
+ console.log(`${marker} ${name} ${chalk.dim(context.apiUrl)}`);
10
+ }
11
+ if (process.env.LOBU_CONTEXT || process.env.LOBU_API_URL) {
12
+ console.log(chalk.dim("\n Environment override is active."));
13
+ if (process.env.LOBU_CONTEXT) {
14
+ console.log(chalk.dim(` LOBU_CONTEXT=${process.env.LOBU_CONTEXT}`));
15
+ }
16
+ if (process.env.LOBU_API_URL) {
17
+ console.log(chalk.dim(` LOBU_API_URL=${process.env.LOBU_API_URL}`));
18
+ }
19
+ }
20
+ console.log();
21
+ }
22
+ export async function contextCurrentCommand() {
23
+ const context = await resolveContext();
24
+ console.log(chalk.bold("\n Current context"));
25
+ console.log(chalk.dim(` Name: ${context.name}`));
26
+ console.log(chalk.dim(` API URL: ${context.apiUrl}`));
27
+ if (context.source === "env") {
28
+ console.log(chalk.dim(" Source: environment override"));
29
+ }
30
+ console.log();
31
+ }
32
+ export async function contextAddCommand(options) {
33
+ await addContext(options.name, options.apiUrl);
34
+ console.log(chalk.green(`\n Saved context ${options.name} -> ${options.apiUrl}\n`));
35
+ }
36
+ export async function contextUseCommand(name) {
37
+ const trimmedName = name.trim();
38
+ const config = await setCurrentContext(trimmedName);
39
+ const context = config.contexts[trimmedName];
40
+ if (!context) {
41
+ throw new Error(`Context ${trimmedName} was not found after update.`);
42
+ }
43
+ console.log(chalk.green(`\n Switched to context ${trimmedName}`));
44
+ console.log(chalk.dim(` API URL: ${context.apiUrl}\n`));
45
+ }
46
+ //# sourceMappingURL=context.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context.js","sourceRoot":"","sources":["../../src/commands/context.ts"],"names":[],"mappings":"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"}
@@ -1,13 +1,6 @@
1
1
  /**
2
2
  * `lobu dev` — smart wrapper around `docker compose up`.
3
- * Reads lobu.toml, seeds .env + MCP config, then passes all args through.
4
- *
5
- * Examples:
6
- * lobu dev → docker compose up
7
- * lobu dev -d → docker compose up -d
8
- * lobu dev --build → docker compose up --build
9
- * lobu dev -d --build → docker compose up -d --build
10
- * lobu dev --force-recreate → docker compose up --force-recreate
3
+ * Reads lobu.toml, seeds .env + agents manifest, then passes all args through.
11
4
  */
12
5
  export declare function devCommand(cwd: string, passthroughArgs: string[]): Promise<void>;
13
6
  //# sourceMappingURL=dev.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"dev.d.ts","sourceRoot":"","sources":["../../src/commands/dev.ts"],"names":[],"mappings":"AAQA;;;;;;;;;;GAUG;AACH,wBAAsB,UAAU,CAC9B,GAAG,EAAE,MAAM,EACX,eAAe,EAAE,MAAM,EAAE,GACxB,OAAO,CAAC,IAAI,CAAC,CA4Ff"}
1
+ {"version":3,"file":"dev.d.ts","sourceRoot":"","sources":["../../src/commands/dev.ts"],"names":[],"mappings":"AAkBA;;;GAGG;AACH,wBAAsB,UAAU,CAC9B,GAAG,EAAE,MAAM,EACX,eAAe,EAAE,MAAM,EAAE,GACxB,OAAO,CAAC,IAAI,CAAC,CA0If"}
@@ -1,20 +1,13 @@
1
- import { mkdir, readFile, writeFile } from "node:fs/promises";
2
- import { join } from "node:path";
3
1
  import { spawn } from "node:child_process";
2
+ import { mkdir, readFile, writeFile } from "node:fs/promises";
3
+ import { basename, join, resolve } from "node:path";
4
4
  import chalk from "chalk";
5
5
  import ora from "ora";
6
- import { isLoadError, loadConfig } from "../config/loader.js";
7
- import { transformConfig } from "../config/transformer.js";
6
+ import { loadSkillsRegistry } from "../commands/skills/registry.js";
7
+ import { isLoadError, loadAgentMarkdown, loadConfig, loadSkillFiles, } from "../config/loader.js";
8
8
  /**
9
9
  * `lobu dev` — smart wrapper around `docker compose up`.
10
- * Reads lobu.toml, seeds .env + MCP config, then passes all args through.
11
- *
12
- * Examples:
13
- * lobu dev → docker compose up
14
- * lobu dev -d → docker compose up -d
15
- * lobu dev --build → docker compose up --build
16
- * lobu dev -d --build → docker compose up -d --build
17
- * lobu dev --force-recreate → docker compose up --force-recreate
10
+ * Reads lobu.toml, seeds .env + agents manifest, then passes all args through.
18
11
  */
19
12
  export async function devCommand(cwd, passthroughArgs) {
20
13
  const result = await loadConfig(cwd);
@@ -31,8 +24,9 @@ export async function devCommand(cwd, passthroughArgs) {
31
24
  const { config } = result;
32
25
  const spinner = ora("Preparing local dev environment...").start();
33
26
  try {
34
- const { envVars, mcpConfig } = transformConfig(config);
35
- // Write .env from lobu.toml-derived vars (merge with existing .env to preserve secrets)
27
+ const lobuDir = join(cwd, ".lobu");
28
+ await mkdir(lobuDir, { recursive: true });
29
+ // Parse .env first so we can resolve $VAR references in lobu.toml
36
30
  const envPath = join(cwd, ".env");
37
31
  let existingEnv = "";
38
32
  try {
@@ -41,45 +35,64 @@ export async function devCommand(cwd, passthroughArgs) {
41
35
  catch {
42
36
  // No existing .env, start fresh
43
37
  }
44
- const existingVars = parseEnvFile(existingEnv);
45
- // Existing vars take precedence user's .env secrets aren't overwritten
46
- const mergedVars = { ...envVars, ...existingVars };
47
- const envContent = Object.entries(mergedVars)
48
- .map(([k, v]) => `${k}=${v}`)
49
- .join("\n");
50
- await writeFile(envPath, envContent + "\n");
51
- // Write MCP config if needed
52
- if (mcpConfig) {
53
- const lobuDir = join(cwd, ".lobu");
54
- await mkdir(lobuDir, { recursive: true });
55
- await writeFile(join(lobuDir, "mcp.config.json"), JSON.stringify({ mcpServers: mcpConfig }, null, 2));
56
- }
57
- // Load IDENTITY.md, SOUL.md, USER.md if they exist
58
- await seedMarkdownFiles(cwd, mergedVars);
38
+ const dotenvVars = parseEnvFile(existingEnv);
39
+ const { manifest, envVars } = await buildManifest(cwd, config.agents, dotenvVars);
40
+ await writeFile(join(lobuDir, "agents.json"), JSON.stringify(manifest, null, 2));
41
+ // Merge derived vars into existing .env (preserves comments and formatting)
42
+ await mergeEnvFile(envPath, existingEnv, envVars);
59
43
  spinner.succeed("Environment prepared from lobu.toml");
60
44
  // Check for docker-compose.yml
61
- const composePath = join(cwd, "docker-compose.yml");
62
45
  try {
63
- await readFile(composePath, "utf-8");
46
+ await readFile(join(cwd, "docker-compose.yml"), "utf-8");
64
47
  }
65
48
  catch {
66
49
  console.log(chalk.yellow("\n No docker-compose.yml found. Run `lobu init` to generate one.\n"));
67
50
  process.exit(1);
68
51
  }
69
- // Pass everything through to docker compose up
70
- console.log(chalk.cyan(`\n Starting ${config.agent.name}...\n`));
71
- const composeArgs = ["compose", "up", ...passthroughArgs];
72
- const child = spawn("docker", composeArgs, {
73
- cwd,
74
- stdio: "inherit",
75
- });
76
- child.on("error", (err) => {
52
+ const fallbackPort = dotenvVars.GATEWAY_PORT || "8080";
53
+ console.log(chalk.cyan(`\n Starting ${manifest.agents.length} agent(s)...\n`));
54
+ const explicitDetach = passthroughArgs.includes("-d") || passthroughArgs.includes("--detach");
55
+ // Always start detached so we can print the banner before logs
56
+ const upArgs = explicitDetach
57
+ ? ["compose", "up", ...passthroughArgs]
58
+ : ["compose", "up", "-d", ...passthroughArgs];
59
+ const up = spawn("docker", upArgs, { cwd, stdio: "inherit" });
60
+ up.on("error", (err) => {
77
61
  console.error(chalk.red(`\n Failed to start docker compose: ${err.message}`));
78
62
  console.log(chalk.dim(" Make sure Docker Desktop is running.\n"));
79
63
  process.exit(1);
80
64
  });
81
- child.on("exit", (code) => {
82
- process.exit(code ?? 0);
65
+ up.on("exit", (code) => {
66
+ if (code !== 0) {
67
+ process.exit(code ?? 1);
68
+ }
69
+ // Detect actual host port from the running container
70
+ const portProbe = spawn("docker", ["compose", "port", "gateway", "8080"], { cwd, stdio: ["ignore", "pipe", "ignore"] });
71
+ let portOutput = "";
72
+ portProbe.stdout.on("data", (data) => {
73
+ portOutput += data.toString();
74
+ });
75
+ portProbe.on("exit", () => {
76
+ const match = portOutput.trim().match(/:(\d+)$/);
77
+ const port = match ? match[1] : fallbackPort;
78
+ const gatewayUrl = `http://localhost:${port}`;
79
+ console.log(chalk.green("\n Lobu is running!\n"));
80
+ console.log(chalk.cyan(` Admin page: ${gatewayUrl}/agents`));
81
+ console.log(chalk.dim(`\n Stop: docker compose down`));
82
+ if (explicitDetach) {
83
+ console.log(chalk.dim(` View logs: docker compose logs -f gateway\n`));
84
+ process.exit(0);
85
+ }
86
+ console.log(chalk.dim(` Ctrl+C stops log tail, containers keep running.\n`));
87
+ // Tail only gateway logs (skip redis noise)
88
+ const logs = spawn("docker", ["compose", "logs", "-f", "gateway"], {
89
+ cwd,
90
+ stdio: "inherit",
91
+ });
92
+ logs.on("exit", (logCode) => {
93
+ process.exit(logCode ?? 0);
94
+ });
95
+ });
83
96
  });
84
97
  }
85
98
  catch (err) {
@@ -89,26 +102,156 @@ export async function devCommand(cwd, passthroughArgs) {
89
102
  }
90
103
  }
91
104
  /**
92
- * Read IDENTITY.md, SOUL.md, USER.md and add their content as env vars
93
- * so the gateway can seed agent settings on startup.
105
+ * Build agents manifest and merged env vars from [agents.*] config.
94
106
  */
95
- async function seedMarkdownFiles(cwd, envVars) {
96
- const files = [
97
- { path: "IDENTITY.md", envKey: "AGENT_IDENTITY_MD" },
98
- { path: "SOUL.md", envKey: "AGENT_SOUL_MD" },
99
- { path: "USER.md", envKey: "AGENT_USER_MD" },
100
- ];
101
- for (const { path, envKey } of files) {
102
- try {
103
- const content = await readFile(join(cwd, path), "utf-8");
104
- if (content.trim()) {
105
- envVars[envKey] = content.trim();
107
+ async function buildManifest(cwd, agents, dotenvVars) {
108
+ const entries = [];
109
+ const rootSkillsDir = join(cwd, "skills");
110
+ const registrySkills = new Map(loadSkillsRegistry().map((skill) => [skill.id, skill]));
111
+ for (const [agentId, agentConfig] of Object.entries(agents)) {
112
+ const agentDir = resolve(cwd, agentConfig.dir);
113
+ const markdown = await loadAgentMarkdown(agentDir);
114
+ const skillFiles = await loadSkillFiles([
115
+ rootSkillsDir,
116
+ join(agentDir, "skills"),
117
+ ]);
118
+ const systemSkills = agentConfig.skills.enabled
119
+ .map((skillId) => registrySkills.get(skillId))
120
+ .filter((skill) => !!skill)
121
+ .map((skill) => ({
122
+ repo: `system/${skill.id}`,
123
+ name: skill.name,
124
+ description: skill.description,
125
+ enabled: true,
126
+ system: true,
127
+ content: "",
128
+ integrations: skill.integrations?.map((integration) => ({
129
+ id: integration.id,
130
+ label: integration.label,
131
+ authType: integration.authType,
132
+ scopesConfig: integration.scopesConfig,
133
+ apiDomains: integration.apiDomains,
134
+ })),
135
+ mcpServers: skill.mcpServers?.map((mcp) => ({
136
+ id: mcp.id,
137
+ name: mcp.name,
138
+ url: mcp.url,
139
+ type: mcp.type,
140
+ command: mcp.command,
141
+ args: mcp.args,
142
+ })),
143
+ nixPackages: skill.nixPackages,
144
+ permissions: skill.permissions,
145
+ providers: skill.providers?.length ? [skill.id] : undefined,
146
+ }));
147
+ const localSkills = skillFiles.map((skillFile) => ({
148
+ repo: `local/${skillFile.name}`,
149
+ name: skillFile.name,
150
+ content: skillFile.content,
151
+ enabled: true,
152
+ }));
153
+ const entry = {
154
+ agentId,
155
+ name: agentConfig.name,
156
+ description: agentConfig.description,
157
+ settings: { ...markdown },
158
+ };
159
+ if (agentConfig.providers.length > 0) {
160
+ entry.settings.installedProviders = agentConfig.providers.map((p) => ({
161
+ providerId: p.id,
162
+ }));
163
+ entry.settings.modelSelection = { mode: "auto" };
164
+ const providerModelPreferences = Object.fromEntries(agentConfig.providers
165
+ .filter((provider) => !!provider.model?.trim())
166
+ .map((provider) => [provider.id, provider.model.trim()]));
167
+ if (Object.keys(providerModelPreferences).length > 0) {
168
+ entry.settings.providerModelPreferences = providerModelPreferences;
106
169
  }
107
170
  }
108
- catch {
109
- // File doesn't exist, skip
171
+ if (systemSkills.length > 0 || localSkills.length > 0) {
172
+ entry.settings.skillsConfig = {
173
+ skills: [...systemSkills, ...localSkills],
174
+ };
110
175
  }
176
+ if (agentConfig.network) {
177
+ entry.settings.networkConfig = {
178
+ allowedDomains: agentConfig.network.allowed,
179
+ deniedDomains: agentConfig.network.denied,
180
+ };
181
+ }
182
+ if (agentConfig.worker?.nix_packages?.length) {
183
+ entry.settings.nixConfig = {
184
+ packages: agentConfig.worker.nix_packages,
185
+ };
186
+ }
187
+ if (agentConfig.skills.mcp) {
188
+ const mcpServers = {};
189
+ for (const [id, mcp] of Object.entries(agentConfig.skills.mcp)) {
190
+ const mapped = { ...mcp };
191
+ if (mcp.oauth) {
192
+ mapped.oauth = {
193
+ authUrl: mcp.oauth.auth_url,
194
+ tokenUrl: mcp.oauth.token_url,
195
+ clientId: resolveEnvVar(mcp.oauth.client_id || "", dotenvVars),
196
+ clientSecret: resolveEnvVar(mcp.oauth.client_secret || "", dotenvVars),
197
+ scopes: mcp.oauth.scopes,
198
+ tokenEndpointAuthMethod: mcp.oauth.token_endpoint_auth_method,
199
+ };
200
+ }
201
+ // Resolve env vars in MCP env block
202
+ if (mcp.env) {
203
+ mapped.env = Object.fromEntries(Object.entries(mcp.env).map(([k, v]) => [
204
+ k,
205
+ resolveEnvVar(v, dotenvVars),
206
+ ]));
207
+ }
208
+ mcpServers[id] = mapped;
209
+ }
210
+ entry.settings.mcpServers = mcpServers;
211
+ }
212
+ // Resolve provider credentials from $ENV_VAR references
213
+ const credentials = agentConfig.providers
214
+ .filter((p) => p.key)
215
+ .map((p) => ({
216
+ providerId: p.id,
217
+ key: resolveEnvVar(p.key, dotenvVars),
218
+ }))
219
+ .filter((c) => c.key); // skip if env var not found
220
+ if (credentials.length > 0) {
221
+ entry.credentials = credentials;
222
+ }
223
+ // Resolve connection configs from $ENV_VAR references
224
+ const connections = agentConfig.connections
225
+ .map((conn) => ({
226
+ type: conn.type,
227
+ config: Object.fromEntries(Object.entries(conn.config).map(([k, v]) => [
228
+ k,
229
+ resolveEnvVar(v, dotenvVars),
230
+ ])),
231
+ }))
232
+ .filter((conn) => Object.values(conn.config).every((v) => v !== "")); // skip if any env var missing
233
+ if (connections.length > 0) {
234
+ entry.connections = connections;
235
+ }
236
+ entries.push(entry);
237
+ }
238
+ const envVars = {
239
+ COMPOSE_PROJECT_NAME: entries[0]?.name
240
+ ? entries[0].name.toLowerCase().replace(/\s+/g, "-")
241
+ : basename(cwd),
242
+ };
243
+ return { manifest: { version: 1, agents: entries }, envVars };
244
+ }
245
+ /**
246
+ * Resolve a value that may be a $ENV_VAR reference.
247
+ * Returns the resolved value, or empty string if the env var is not set.
248
+ */
249
+ function resolveEnvVar(value, envVars) {
250
+ if (value.startsWith("$")) {
251
+ const varName = value.slice(1);
252
+ return envVars[varName] || process.env[varName] || "";
111
253
  }
254
+ return value;
112
255
  }
113
256
  function parseEnvFile(content) {
114
257
  const vars = {};
@@ -120,9 +263,45 @@ function parseEnvFile(content) {
120
263
  if (eqIdx === -1)
121
264
  continue;
122
265
  const key = trimmed.slice(0, eqIdx);
123
- const value = trimmed.slice(eqIdx + 1);
266
+ let value = trimmed.slice(eqIdx + 1);
267
+ // Strip surrounding quotes (double or single)
268
+ if ((value.startsWith('"') && value.endsWith('"')) ||
269
+ (value.startsWith("'") && value.endsWith("'"))) {
270
+ value = value.slice(1, -1);
271
+ }
124
272
  vars[key] = value;
125
273
  }
126
274
  return vars;
127
275
  }
276
+ /**
277
+ * Merge derived env vars into an existing .env file.
278
+ * Updates existing keys in-place and appends new ones at the end.
279
+ * Preserves comments, blank lines, and formatting.
280
+ */
281
+ async function mergeEnvFile(envPath, existingContent, newVars) {
282
+ const remaining = { ...newVars };
283
+ const lines = existingContent.split("\n");
284
+ const updated = lines.map((line) => {
285
+ const trimmed = line.trim();
286
+ if (!trimmed || trimmed.startsWith("#"))
287
+ return line;
288
+ const eqIdx = trimmed.indexOf("=");
289
+ if (eqIdx === -1)
290
+ return line;
291
+ const key = trimmed.slice(0, eqIdx);
292
+ if (key in remaining) {
293
+ const val = remaining[key];
294
+ delete remaining[key];
295
+ return `${key}=${val}`;
296
+ }
297
+ return line;
298
+ });
299
+ // Append any new vars that weren't already in the file
300
+ for (const [key, value] of Object.entries(remaining)) {
301
+ updated.push(`${key}=${value}`);
302
+ }
303
+ // Ensure trailing newline
304
+ const content = `${updated.join("\n").trimEnd()}\n`;
305
+ await writeFile(envPath, content);
306
+ }
128
307
  //# sourceMappingURL=dev.js.map