@lobu/cli 6.0.1 → 7.0.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 (265) hide show
  1. package/README.md +20 -27
  2. package/dist/bundled-skills/lobu/SKILL.md +11 -11
  3. package/dist/commands/_lib/apply/apply-cmd.d.ts +38 -0
  4. package/dist/commands/_lib/apply/apply-cmd.d.ts.map +1 -1
  5. package/dist/commands/_lib/apply/apply-cmd.js +574 -40
  6. package/dist/commands/_lib/apply/apply-cmd.js.map +1 -1
  7. package/dist/commands/_lib/apply/client.d.ts +180 -1
  8. package/dist/commands/_lib/apply/client.d.ts.map +1 -1
  9. package/dist/commands/_lib/apply/client.js +308 -28
  10. package/dist/commands/_lib/apply/client.js.map +1 -1
  11. package/dist/commands/_lib/apply/desired-state.d.ts +134 -3
  12. package/dist/commands/_lib/apply/desired-state.d.ts.map +1 -1
  13. package/dist/commands/_lib/apply/desired-state.js +703 -89
  14. package/dist/commands/_lib/apply/desired-state.js.map +1 -1
  15. package/dist/commands/_lib/apply/diff.d.ts +61 -3
  16. package/dist/commands/_lib/apply/diff.d.ts.map +1 -1
  17. package/dist/commands/_lib/apply/diff.js +382 -92
  18. package/dist/commands/_lib/apply/diff.js.map +1 -1
  19. package/dist/commands/_lib/apply/prompt.d.ts +6 -0
  20. package/dist/commands/_lib/apply/prompt.d.ts.map +1 -1
  21. package/dist/commands/_lib/apply/prompt.js +16 -0
  22. package/dist/commands/_lib/apply/prompt.js.map +1 -1
  23. package/dist/commands/_lib/apply/render.d.ts +9 -0
  24. package/dist/commands/_lib/apply/render.d.ts.map +1 -1
  25. package/dist/commands/_lib/apply/render.js +80 -3
  26. package/dist/commands/_lib/apply/render.js.map +1 -1
  27. package/dist/commands/agent.d.ts +7 -0
  28. package/dist/commands/agent.d.ts.map +1 -1
  29. package/dist/commands/agent.js +65 -1
  30. package/dist/commands/agent.js.map +1 -1
  31. package/dist/commands/chat.d.ts +12 -9
  32. package/dist/commands/chat.d.ts.map +1 -1
  33. package/dist/commands/chat.js +125 -57
  34. package/dist/commands/chat.js.map +1 -1
  35. package/dist/commands/dev.d.ts +23 -7
  36. package/dist/commands/dev.d.ts.map +1 -1
  37. package/dist/commands/dev.js +197 -49
  38. package/dist/commands/dev.js.map +1 -1
  39. package/dist/commands/doctor.d.ts +1 -0
  40. package/dist/commands/doctor.d.ts.map +1 -1
  41. package/dist/commands/doctor.js +136 -0
  42. package/dist/commands/doctor.js.map +1 -1
  43. package/dist/commands/eval.d.ts +8 -0
  44. package/dist/commands/eval.d.ts.map +1 -1
  45. package/dist/commands/eval.js +72 -6
  46. package/dist/commands/eval.js.map +1 -1
  47. package/dist/commands/init.d.ts +22 -5
  48. package/dist/commands/init.d.ts.map +1 -1
  49. package/dist/commands/init.js +355 -182
  50. package/dist/commands/init.js.map +1 -1
  51. package/dist/commands/link.d.ts +11 -0
  52. package/dist/commands/link.d.ts.map +1 -0
  53. package/dist/commands/link.js +28 -0
  54. package/dist/commands/link.js.map +1 -0
  55. package/dist/commands/login.d.ts.map +1 -1
  56. package/dist/commands/login.js +14 -2
  57. package/dist/commands/login.js.map +1 -1
  58. package/dist/commands/memory/_lib/browser-auth-cmd.d.ts.map +1 -1
  59. package/dist/commands/memory/_lib/browser-auth-cmd.js +3 -3
  60. package/dist/commands/memory/_lib/browser-auth-cmd.js.map +1 -1
  61. package/dist/commands/memory/_lib/mcp.d.ts +2 -2
  62. package/dist/commands/memory/_lib/mcp.d.ts.map +1 -1
  63. package/dist/commands/memory/_lib/mcp.js +24 -12
  64. package/dist/commands/memory/_lib/mcp.js.map +1 -1
  65. package/dist/commands/memory/_lib/openclaw-auth.d.ts +1 -0
  66. package/dist/commands/memory/_lib/openclaw-auth.d.ts.map +1 -1
  67. package/dist/commands/memory/_lib/openclaw-auth.js +14 -3
  68. package/dist/commands/memory/_lib/openclaw-auth.js.map +1 -1
  69. package/dist/commands/memory/_lib/openclaw-cmd.js +1 -1
  70. package/dist/commands/memory/_lib/openclaw-cmd.js.map +1 -1
  71. package/dist/commands/memory/_lib/schema.d.ts +29 -2
  72. package/dist/commands/memory/_lib/schema.d.ts.map +1 -1
  73. package/dist/commands/memory/_lib/schema.js +121 -5
  74. package/dist/commands/memory/_lib/schema.js.map +1 -1
  75. package/dist/commands/memory/_lib/seed-cmd.d.ts.map +1 -1
  76. package/dist/commands/memory/_lib/seed-cmd.js +46 -24
  77. package/dist/commands/memory/_lib/seed-cmd.js.map +1 -1
  78. package/dist/commands/memory/run.d.ts.map +1 -1
  79. package/dist/commands/memory/run.js +2 -2
  80. package/dist/commands/memory/run.js.map +1 -1
  81. package/dist/commands/org.d.ts +4 -0
  82. package/dist/commands/org.d.ts.map +1 -1
  83. package/dist/commands/org.js +10 -0
  84. package/dist/commands/org.js.map +1 -1
  85. package/dist/commands/platforms/platform-prompts.d.ts +0 -1
  86. package/dist/commands/platforms/platform-prompts.d.ts.map +1 -1
  87. package/dist/commands/platforms/platform-prompts.js +54 -8
  88. package/dist/commands/platforms/platform-prompts.js.map +1 -1
  89. package/dist/commands/telemetry.d.ts +10 -0
  90. package/dist/commands/telemetry.d.ts.map +1 -0
  91. package/dist/commands/telemetry.js +68 -0
  92. package/dist/commands/telemetry.js.map +1 -0
  93. package/dist/commands/token.d.ts +9 -0
  94. package/dist/commands/token.d.ts.map +1 -1
  95. package/dist/commands/token.js +54 -0
  96. package/dist/commands/token.js.map +1 -1
  97. package/dist/commands/whoami.d.ts.map +1 -1
  98. package/dist/commands/whoami.js +1 -1
  99. package/dist/commands/whoami.js.map +1 -1
  100. package/dist/connectors/README.md +534 -0
  101. package/dist/connectors/__tests__/browser-scraper-utils.test.ts +186 -0
  102. package/dist/connectors/apple_health.ts +138 -0
  103. package/dist/connectors/apple_screen_time.ts +82 -0
  104. package/dist/connectors/browser-scraper-utils.ts +246 -0
  105. package/dist/connectors/capterra.ts +277 -0
  106. package/dist/connectors/g2.ts +290 -0
  107. package/dist/connectors/github.ts +1530 -0
  108. package/dist/connectors/glassdoor.ts +295 -0
  109. package/dist/connectors/gmaps.ts +197 -0
  110. package/dist/connectors/google_calendar.ts +641 -0
  111. package/dist/connectors/google_gmail.ts +754 -0
  112. package/dist/connectors/google_photos.ts +776 -0
  113. package/dist/connectors/google_play.ts +349 -0
  114. package/dist/connectors/hackernews.ts +471 -0
  115. package/dist/connectors/index.ts +28 -0
  116. package/dist/connectors/ios_appstore.ts +226 -0
  117. package/dist/connectors/linkedin.ts +494 -0
  118. package/dist/connectors/local_directory.ts +91 -0
  119. package/dist/connectors/microsoft_outlook.ts +410 -0
  120. package/dist/connectors/producthunt.ts +471 -0
  121. package/dist/connectors/reddit.ts +600 -0
  122. package/dist/connectors/revolut.ts +572 -0
  123. package/dist/connectors/rss.ts +448 -0
  124. package/dist/connectors/spotify.ts +590 -0
  125. package/dist/connectors/trustpilot.ts +203 -0
  126. package/dist/connectors/website.ts +629 -0
  127. package/dist/connectors/whatsapp.ts +1081 -0
  128. package/dist/connectors/whatsapp_local.ts +125 -0
  129. package/dist/connectors/x.ts +536 -0
  130. package/dist/connectors/youtube.ts +666 -0
  131. package/dist/db/migrations/00000000000000_baseline.sql +4867 -0
  132. package/dist/db/migrations/20260405193000_add_mcp_sessions.sql +33 -0
  133. package/dist/db/migrations/20260408120000_remove_system_connectors.sql +48 -0
  134. package/dist/db/migrations/20260408120001_optional_compiled_code.sql +6 -0
  135. package/dist/db/migrations/20260409110000_add_active_watcher_run_index.sql +9 -0
  136. package/dist/db/migrations/20260409130000_connector_default_config.sql +5 -0
  137. package/dist/db/migrations/20260410120000_add_agent_secrets.sql +25 -0
  138. package/dist/db/migrations/20260413170000_add_watcher_group_id.sql +67 -0
  139. package/dist/db/migrations/20260416120000_add_entity_wa_jid_index.sql +14 -0
  140. package/dist/db/migrations/20260417100000_add_entity_identities.sql +77 -0
  141. package/dist/db/migrations/20260418100000_add_auth_runs.sql +83 -0
  142. package/dist/db/migrations/20260418110000_add_runs_created_by_user.sql +18 -0
  143. package/dist/db/migrations/20260419120000_add_event_identity_indexes.sql +56 -0
  144. package/dist/db/migrations/20260420120000_extend_reserved_org_slugs.sql +56 -0
  145. package/dist/db/migrations/20260424030000_add_watcher_run_correlation.sql +52 -0
  146. package/dist/db/migrations/20260424130000_relax_events_client_id_fk.sql +47 -0
  147. package/dist/db/migrations/20260425100000_normalize_watcher_feedback.sql +91 -0
  148. package/dist/db/migrations/20260425120000_add_run_diagnostics.sql +20 -0
  149. package/dist/db/migrations/20260425130000_add_repair_agent_plumbing.sql +46 -0
  150. package/dist/db/migrations/20260426120000_entities_entity_type_fk.sql +101 -0
  151. package/dist/db/migrations/20260426130000_db_integrity_cleanup.sql +104 -0
  152. package/dist/db/migrations/20260426130001_db_integrity_cleanup_concurrent.sql +187 -0
  153. package/dist/db/migrations/20260427133000_events_created_by_nullable.sql +74 -0
  154. package/dist/db/migrations/20260427140000_identity_engine_indexes.sql +140 -0
  155. package/dist/db/migrations/20260427150000_drop_events_source_id.sql +177 -0
  156. package/dist/db/migrations/20260427160000_drop_dead_schema.sql +76 -0
  157. package/dist/db/migrations/20260427170000_market_founder_to_member.sql +364 -0
  158. package/dist/db/migrations/20260428040000_cascade_events_watchers_org_fk.sql +66 -0
  159. package/dist/db/migrations/20260428050000_add_runs_approved_input.sql +9 -0
  160. package/dist/db/migrations/20260429010000_auth_profile_tenant_scoped_fk.sql +79 -0
  161. package/dist/db/migrations/20260429060000_extend_runs_for_lobu_queue.sql +108 -0
  162. package/dist/db/migrations/20260429120000_agent_changed_notify.sql +97 -0
  163. package/dist/db/migrations/20260429120100_user_auth_profiles_and_model_prefs.sql +36 -0
  164. package/dist/db/migrations/20260429120200_fix_notify_old_keys.sql +130 -0
  165. package/dist/db/migrations/20260429130000_oauth_states_cli_sessions_rate_limits.sql +83 -0
  166. package/dist/db/migrations/20260429140000_phase8_grants_chat_connections_mcp_sessions.sql +84 -0
  167. package/dist/db/migrations/20260429140100_runs_priority_expires_at_retry_delay.sql +44 -0
  168. package/dist/db/migrations/20260429180000_drop_invalidatable_cache_triggers.sql +25 -0
  169. package/dist/db/migrations/20260430005614_agents_apply_fields.sql +21 -0
  170. package/dist/db/migrations/20260430022231_fix_connection_config_encryption.sql +69 -0
  171. package/dist/db/migrations/20260430151215_add_task_run_type.sql +77 -0
  172. package/dist/db/migrations/20260501000000_drop_cli_sessions.sql +27 -0
  173. package/dist/db/migrations/20260501133000_lobu_memory_mcp_id.sql +117 -0
  174. package/dist/db/migrations/20260502000000_drop_chat_connections.sql +60 -0
  175. package/dist/db/migrations/20260503000000_agent_secrets_org_scope.sql +56 -0
  176. package/dist/db/migrations/20260504000000_flatten_agents_drop_sandbox_model.sql +48 -0
  177. package/dist/db/migrations/20260510220000_connector_required_capability.sql +47 -0
  178. package/dist/db/migrations/20260512000000_device_worker_connection_binding.sql +113 -0
  179. package/dist/db/migrations/20260512131703_connections_slug.sql +131 -0
  180. package/dist/db/migrations/20260513000000_chat_user_identities.sql +24 -0
  181. package/dist/db/migrations/20260513120000_auth_profiles_device_binding.sql +50 -0
  182. package/dist/db/migrations/20260513150000_auth_profiles_cdp_url.sql +43 -0
  183. package/dist/db/migrations/20260513200000_notifications_as_events.sql +86 -0
  184. package/dist/db/migrations/20260514000000_scheduled_jobs.sql +97 -0
  185. package/dist/db/migrations/20260514120000_auth_profiles_connector_key_nullable.sql +42 -0
  186. package/dist/eval/types.d.ts +2 -0
  187. package/dist/eval/types.d.ts.map +1 -1
  188. package/dist/index.d.ts +11 -0
  189. package/dist/index.d.ts.map +1 -1
  190. package/dist/index.js +210 -132
  191. package/dist/index.js.map +1 -1
  192. package/dist/internal/api-client.d.ts +4 -8
  193. package/dist/internal/api-client.d.ts.map +1 -1
  194. package/dist/internal/api-client.js +1 -1
  195. package/dist/internal/api-client.js.map +1 -1
  196. package/dist/internal/context.js +2 -2
  197. package/dist/internal/context.js.map +1 -1
  198. package/dist/internal/credentials.d.ts.map +1 -1
  199. package/dist/internal/credentials.js +6 -1
  200. package/dist/internal/credentials.js.map +1 -1
  201. package/dist/internal/gateway-url.d.ts +14 -0
  202. package/dist/internal/gateway-url.d.ts.map +1 -1
  203. package/dist/internal/gateway-url.js +19 -0
  204. package/dist/internal/gateway-url.js.map +1 -1
  205. package/dist/internal/index.d.ts +3 -4
  206. package/dist/internal/index.d.ts.map +1 -1
  207. package/dist/internal/index.js +3 -3
  208. package/dist/internal/index.js.map +1 -1
  209. package/dist/internal/oauth.d.ts +6 -5
  210. package/dist/internal/oauth.d.ts.map +1 -1
  211. package/dist/internal/oauth.js +2 -2
  212. package/dist/internal/project-link.d.ts +10 -0
  213. package/dist/internal/project-link.d.ts.map +1 -0
  214. package/dist/internal/project-link.js +48 -0
  215. package/dist/internal/project-link.js.map +1 -0
  216. package/dist/providers.json +2 -2
  217. package/dist/server.bundle.mjs +31654 -30866
  218. package/dist/start-local.bundle.mjs +74409 -0
  219. package/dist/templates/README.md.tmpl +10 -11
  220. package/dist/templates/TESTING.md.tmpl +9 -9
  221. package/package.json +15 -13
  222. package/dist/__tests__/chat.integration.test.d.ts +0 -2
  223. package/dist/__tests__/chat.integration.test.d.ts.map +0 -1
  224. package/dist/__tests__/chat.integration.test.js +0 -337
  225. package/dist/__tests__/chat.integration.test.js.map +0 -1
  226. package/dist/__tests__/dev.test.d.ts +0 -2
  227. package/dist/__tests__/dev.test.d.ts.map +0 -1
  228. package/dist/__tests__/dev.test.js +0 -25
  229. package/dist/__tests__/dev.test.js.map +0 -1
  230. package/dist/__tests__/init-memory.test.d.ts +0 -2
  231. package/dist/__tests__/init-memory.test.d.ts.map +0 -1
  232. package/dist/__tests__/init-memory.test.js +0 -45
  233. package/dist/__tests__/init-memory.test.js.map +0 -1
  234. package/dist/__tests__/token.test.d.ts +0 -2
  235. package/dist/__tests__/token.test.d.ts.map +0 -1
  236. package/dist/__tests__/token.test.js +0 -52
  237. package/dist/__tests__/token.test.js.map +0 -1
  238. package/dist/commands/_lib/apply/__tests__/client.test.d.ts +0 -2
  239. package/dist/commands/_lib/apply/__tests__/client.test.d.ts.map +0 -1
  240. package/dist/commands/_lib/apply/__tests__/client.test.js +0 -23
  241. package/dist/commands/_lib/apply/__tests__/client.test.js.map +0 -1
  242. package/dist/commands/_lib/apply/__tests__/desired-state.test.d.ts +0 -2
  243. package/dist/commands/_lib/apply/__tests__/desired-state.test.d.ts.map +0 -1
  244. package/dist/commands/_lib/apply/__tests__/desired-state.test.js +0 -140
  245. package/dist/commands/_lib/apply/__tests__/desired-state.test.js.map +0 -1
  246. package/dist/commands/_lib/apply/__tests__/diff.test.d.ts +0 -2
  247. package/dist/commands/_lib/apply/__tests__/diff.test.d.ts.map +0 -1
  248. package/dist/commands/_lib/apply/__tests__/diff.test.js +0 -378
  249. package/dist/commands/_lib/apply/__tests__/diff.test.js.map +0 -1
  250. package/dist/commands/apply.d.ts +0 -3
  251. package/dist/commands/apply.d.ts.map +0 -1
  252. package/dist/commands/apply.js +0 -5
  253. package/dist/commands/apply.js.map +0 -1
  254. package/dist/commands/memory/_lib/openclaw-auth.test.d.ts +0 -2
  255. package/dist/commands/memory/_lib/openclaw-auth.test.d.ts.map +0 -1
  256. package/dist/commands/memory/_lib/openclaw-auth.test.js +0 -9
  257. package/dist/commands/memory/_lib/openclaw-auth.test.js.map +0 -1
  258. package/dist/internal/__tests__/api-client.test.d.ts +0 -2
  259. package/dist/internal/__tests__/api-client.test.d.ts.map +0 -1
  260. package/dist/internal/__tests__/api-client.test.js +0 -95
  261. package/dist/internal/__tests__/api-client.test.js.map +0 -1
  262. package/dist/internal/__tests__/context.test.d.ts +0 -2
  263. package/dist/internal/__tests__/context.test.d.ts.map +0 -1
  264. package/dist/internal/__tests__/context.test.js +0 -77
  265. package/dist/internal/__tests__/context.test.js.map +0 -1
@@ -1,7 +1,7 @@
1
1
  import { randomBytes } from "node:crypto";
2
2
  import { constants } from "node:fs";
3
- import { access, mkdir, readFile, writeFile } from "node:fs/promises";
4
- import { join } from "node:path";
3
+ import { access, mkdir, readdir, readFile, writeFile } from "node:fs/promises";
4
+ import { basename, join, resolve } from "node:path";
5
5
  import { confirm, input, password, select } from "@inquirer/prompts";
6
6
  import chalk from "chalk";
7
7
  import ora from "ora";
@@ -9,76 +9,138 @@ import { promptPlatformConfig } from "../commands/platforms/platform-prompts.js"
9
9
  import { setLocalEnvValue } from "../internal/local-env.js";
10
10
  import { getProviderById, loadProviderRegistry, } from "../commands/providers/registry.js";
11
11
  import { renderTemplate } from "../utils/template.js";
12
- const DEFAULT_OWLETTO_MCP_URL = "https://lobu.ai/mcp";
13
- export async function initCommand(cwd = process.cwd(), projectNameArg) {
14
- console.log(chalk.bold.cyan("\n🤖 Welcome to Lobu!\n"));
15
- // Get CLI version
12
+ const DEFAULT_LOBU_MCP_URL = "https://lobu.ai/mcp";
13
+ const PROJECT_NAME_PATTERN = /^[a-z0-9-]+$/;
14
+ const PLATFORM_CHOICES = [
15
+ "telegram",
16
+ "slack",
17
+ "discord",
18
+ "whatsapp",
19
+ "teams",
20
+ "gchat",
21
+ ];
22
+ const NETWORK_CHOICES = ["restricted", "open", "isolated"];
23
+ const MEMORY_CHOICES = ["none", "lobu-cloud", "lobu-custom"];
24
+ export async function initCommand(cwd = process.cwd(), projectNameArg, options = {}) {
16
25
  const cliVersion = await getCliVersion();
17
- // Validate project name if provided as argument
18
- if (projectNameArg && !/^[a-z0-9-]+$/.test(projectNameArg)) {
19
- console.log(chalk.red("\n✗ Project name must be lowercase alphanumeric with hyphens only\n"));
26
+ const useDefaults = options.yes === true;
27
+ // Catch flag combos that can't satisfy a prompt before we mkdir anything.
28
+ if (useDefaults && options.memory === "lobu-custom" && !options.memoryUrl) {
29
+ console.error(chalk.red("\n✗ --memory lobu-custom requires --memory-url <url>.\n"));
20
30
  process.exit(1);
21
31
  }
22
- // Interactive prompts - basic setup
23
- const projectName = projectNameArg ||
24
- (await input({
25
- message: "Project name?",
26
- default: "my-lobu",
27
- validate: (value) => {
28
- if (!/^[a-z0-9-]+$/.test(value)) {
29
- return "Project name must be lowercase alphanumeric with hyphens only";
30
- }
31
- return true;
32
- },
33
- }));
34
- const projectDir = join(cwd, projectName);
35
- try {
36
- await access(projectDir, constants.F_OK);
37
- console.log(chalk.red(`\n✗ Directory "${projectName}" already exists. Please choose a different project name or remove the existing directory.\n`));
38
- process.exit(1);
32
+ const here = options.here || projectNameArg === ".";
33
+ let projectName;
34
+ let projectDir;
35
+ if (here) {
36
+ projectDir = cwd;
37
+ projectName = basename(resolve(cwd));
38
+ if (!PROJECT_NAME_PATTERN.test(projectName)) {
39
+ // Common when cwd is e.g. "My Project". Force user to pick a slug.
40
+ projectName =
41
+ projectNameArg && projectNameArg !== "."
42
+ ? projectNameArg
43
+ : await promptOrDefault({
44
+ flag: undefined,
45
+ useDefaults,
46
+ defaultValue: slugify(basename(resolve(cwd))) || "my-lobu",
47
+ prompt: () => input({
48
+ message: "Project slug?",
49
+ default: slugify(basename(resolve(cwd))) || "my-lobu",
50
+ validate: validateProjectName,
51
+ }),
52
+ });
53
+ }
54
+ const entries = await readdir(projectDir).catch(() => []);
55
+ const conflict = entries.some((n) => n === "lobu.toml" || n === "agents" || n === ".env");
56
+ if (conflict) {
57
+ console.log(chalk.red(`\n✗ ${projectDir} already contains a Lobu project (lobu.toml / agents/ / .env).\n Remove them or pick another directory.\n`));
58
+ process.exit(1);
59
+ }
60
+ console.log(chalk.dim(`\nScaffolding into current directory: ${chalk.cyan(projectDir)}\n`));
39
61
  }
40
- catch {
41
- // Directory doesn't exist - good to proceed
42
- await mkdir(projectDir, { recursive: true });
43
- console.log(chalk.dim(`\nCreating project in: ${chalk.cyan(projectDir)}\n`));
62
+ else {
63
+ if (projectNameArg && !PROJECT_NAME_PATTERN.test(projectNameArg)) {
64
+ console.log(chalk.red(`\n✗ Project name must be lowercase alphanumeric with hyphens only (got: ${projectNameArg}).\n`));
65
+ process.exit(1);
66
+ }
67
+ projectName =
68
+ projectNameArg ??
69
+ (await promptOrDefault({
70
+ flag: undefined,
71
+ useDefaults,
72
+ defaultValue: "my-lobu",
73
+ prompt: () => input({
74
+ message: "Project name?",
75
+ default: "my-lobu",
76
+ validate: validateProjectName,
77
+ }),
78
+ }));
79
+ projectDir = join(cwd, projectName);
80
+ try {
81
+ await access(projectDir, constants.F_OK);
82
+ console.log(chalk.red(`\n✗ Directory "${projectName}" already exists. Pick a different name, remove it, or pass \`--here\` to scaffold into the current directory.\n`));
83
+ process.exit(1);
84
+ }
85
+ catch {
86
+ await mkdir(projectDir, { recursive: true });
87
+ console.log(chalk.dim(`\nCreating project in: ${chalk.cyan(projectDir)}\n`));
88
+ }
44
89
  }
45
- // Gateway port selection
46
- const gatewayPort = await input({
47
- message: "Gateway port?",
48
- default: "8787",
90
+ const gatewayPort = await promptOrDefault({
91
+ flag: options.port,
92
+ useDefaults,
93
+ defaultValue: "8787",
49
94
  validate: (value) => {
50
- const port = Number(value);
51
- if (!Number.isInteger(port) || port < 1 || port > 65535) {
52
- return "Please enter a valid port number (1-65535)";
53
- }
54
- return true;
95
+ const p = Number(value);
96
+ return Number.isInteger(p) && p >= 1 && p <= 65535
97
+ ? true
98
+ : "Please enter a valid port (1-65535)";
55
99
  },
56
- });
57
- // Public gateway URL (optional — only needed for OAuth callbacks and external webhooks)
58
- const publicGatewayUrl = await input({
59
- message: "Public gateway URL? (leave empty for local dev, set for OAuth/webhooks)",
60
- default: "",
61
- });
62
- // Worker network access policy
63
- const networkPolicy = await select({
64
- message: "Worker network access?",
65
- choices: [
66
- {
67
- name: "Restricted (recommended) — common registries only (npm, GitHub, PyPI)",
68
- value: "restricted",
69
- },
70
- {
71
- name: "Open — workers can access any domain",
72
- value: "open",
73
- },
74
- {
75
- name: "Isolated — workers have no internet access",
76
- value: "isolated",
100
+ prompt: () => input({
101
+ message: "Gateway port?",
102
+ default: "8787",
103
+ validate: (value) => {
104
+ const p = Number(value);
105
+ if (!Number.isInteger(p) || p < 1 || p > 65535) {
106
+ return "Please enter a valid port number (1-65535)";
107
+ }
108
+ return true;
77
109
  },
78
- ],
79
- default: "restricted",
110
+ }),
111
+ });
112
+ const publicGatewayUrl = await promptOrDefault({
113
+ flag: options.publicUrl,
114
+ useDefaults,
115
+ defaultValue: "",
116
+ prompt: () => input({
117
+ message: "Public gateway URL? (leave empty for local dev, set for OAuth/webhooks)",
118
+ default: "",
119
+ }),
80
120
  });
81
- // Provider selection (from the bundled providers registry)
121
+ const networkPolicy = (await promptOrDefault({
122
+ flag: options.network,
123
+ useDefaults,
124
+ defaultValue: "restricted",
125
+ validate: (v) => NETWORK_CHOICES.includes(v)
126
+ ? true
127
+ : `Must be one of: ${NETWORK_CHOICES.join(", ")}`,
128
+ prompt: () => select({
129
+ message: "Worker network access?",
130
+ choices: [
131
+ {
132
+ name: "Restricted (recommended) — common registries only (npm, GitHub, PyPI)",
133
+ value: "restricted",
134
+ },
135
+ { name: "Open — workers can access any domain", value: "open" },
136
+ {
137
+ name: "Isolated — workers have no internet access",
138
+ value: "isolated",
139
+ },
140
+ ],
141
+ default: "restricted",
142
+ }),
143
+ }));
82
144
  const providerSkills = loadProviderRegistry();
83
145
  const providerChoices = [
84
146
  { name: "Skip — I'll add a provider later", value: "" },
@@ -87,10 +149,21 @@ export async function initCommand(cwd = process.cwd(), projectNameArg) {
87
149
  value: s.id,
88
150
  })),
89
151
  ];
90
- const providerId = await select({
91
- message: "AI provider?",
92
- choices: providerChoices,
93
- default: "",
152
+ const providerId = await promptOrDefault({
153
+ flag: options.provider,
154
+ useDefaults,
155
+ defaultValue: "",
156
+ validate: (v) => v === "" || providerChoices.some((c) => c.value === v)
157
+ ? true
158
+ : `Unknown provider "${v}". Available: ${providerChoices
159
+ .filter((c) => c.value)
160
+ .map((c) => c.value)
161
+ .join(", ")}`,
162
+ prompt: () => select({
163
+ message: "AI provider?",
164
+ choices: providerChoices,
165
+ default: "",
166
+ }),
94
167
  });
95
168
  let providerApiKey = "";
96
169
  let selectedProvider;
@@ -98,15 +171,21 @@ export async function initCommand(cwd = process.cwd(), projectNameArg) {
98
171
  selectedProvider = getProviderById(providerId);
99
172
  const p = selectedProvider?.providers?.[0];
100
173
  if (p) {
101
- providerApiKey = await password({
102
- message: `${p.displayName} API key:`,
103
- mask: true,
104
- });
174
+ if (options.providerKey) {
175
+ providerApiKey = options.providerKey;
176
+ }
177
+ else if (process.env[p.envVarName]) {
178
+ // Inherit from env so `--yes` can pick up keys set in the shell.
179
+ providerApiKey = process.env[p.envVarName] ?? "";
180
+ }
181
+ else if (!useDefaults) {
182
+ providerApiKey = await password({
183
+ message: `${p.displayName} API key:`,
184
+ mask: true,
185
+ });
186
+ }
105
187
  }
106
188
  }
107
- // Define skills locally via skills/<name>/SKILL.md or
108
- // agents/<id>/skills/<name>/SKILL.md.
109
- // Chat platform selection
110
189
  const platformChoices = [
111
190
  { name: "Skip — I'll connect a platform later", value: "" },
112
191
  { name: "Telegram", value: "telegram" },
@@ -116,42 +195,81 @@ export async function initCommand(cwd = process.cwd(), projectNameArg) {
116
195
  { name: "Microsoft Teams", value: "teams" },
117
196
  { name: "Google Chat", value: "gchat" },
118
197
  ];
119
- const platformType = await select({
120
- message: "Connect a chat platform?",
121
- choices: platformChoices,
122
- default: "",
198
+ const platformType = await promptOrDefault({
199
+ flag: options.platform,
200
+ useDefaults,
201
+ defaultValue: "",
202
+ validate: (v) => v === "" || PLATFORM_CHOICES.includes(v)
203
+ ? true
204
+ : `Unknown platform "${v}". Available: ${PLATFORM_CHOICES.join(", ")}`,
205
+ prompt: () => select({
206
+ message: "Connect a chat platform?",
207
+ choices: platformChoices,
208
+ default: "",
209
+ }),
123
210
  });
124
- const { platformConfig, platformSecrets } = platformType
125
- ? await promptPlatformConfig(platformType)
126
- : { platformConfig: {}, platformSecrets: [] };
127
- // Memory
128
- const memoryChoice = await select({
129
- message: "Memory:",
130
- choices: [
131
- { name: "None (filesystem memory)", value: "none" },
132
- { name: "Lobu Cloud (app.lobu.ai)", value: "owletto-cloud" },
133
- { name: "Custom Lobu memory URL", value: "owletto-custom" },
134
- ],
135
- default: "none",
211
+ // Interactive: prompt for real secrets. --yes: write placeholder env-var
212
+ // refs into lobu.toml; the user fills .env afterwards.
213
+ let platformConfig = {};
214
+ let platformSecrets = [];
215
+ if (platformType) {
216
+ if (useDefaults) {
217
+ platformConfig = PLATFORM_PLACEHOLDERS[platformType];
218
+ }
219
+ else {
220
+ ({ platformConfig, platformSecrets } =
221
+ await promptPlatformConfig(platformType));
222
+ }
223
+ }
224
+ const enableSlackPreview = await promptBooleanOrDefault({
225
+ flag: options.slackPreview,
226
+ useDefaults,
227
+ defaultValue: false,
228
+ prompt: () => confirm({
229
+ message: "Enable Slack Preview with the public Lobu Developer Slack bot?",
230
+ default: false,
231
+ }),
136
232
  });
233
+ const memoryChoice = (await promptOrDefault({
234
+ flag: options.memory,
235
+ useDefaults,
236
+ defaultValue: "none",
237
+ validate: (v) => MEMORY_CHOICES.includes(v)
238
+ ? true
239
+ : `Must be one of: ${MEMORY_CHOICES.join(", ")}`,
240
+ prompt: () => select({
241
+ message: "Memory:",
242
+ choices: [
243
+ { name: "None (filesystem memory)", value: "none" },
244
+ { name: "Lobu Cloud (app.lobu.ai)", value: "lobu-cloud" },
245
+ { name: "Custom Lobu memory URL", value: "lobu-custom" },
246
+ ],
247
+ default: "none",
248
+ }),
249
+ }));
137
250
  const envSecrets = [];
138
- const includeOwlettoMemory = memoryChoice !== "none";
139
- let owlettoUrl = "";
140
- if (memoryChoice === "owletto-cloud") {
141
- owlettoUrl = DEFAULT_OWLETTO_MCP_URL;
251
+ const includeLobuMemory = memoryChoice !== "none";
252
+ let lobuUrl = "";
253
+ if (memoryChoice === "lobu-cloud") {
254
+ lobuUrl = DEFAULT_LOBU_MCP_URL;
142
255
  }
143
- else if (memoryChoice === "owletto-custom") {
144
- owlettoUrl = await input({
145
- message: "Lobu memory MCP URL:",
146
- validate: (v) => (v ? true : "URL is required"),
147
- });
148
- envSecrets.push({ envVar: "MEMORY_URL", value: owlettoUrl });
256
+ else if (memoryChoice === "lobu-custom") {
257
+ lobuUrl =
258
+ options.memoryUrl ??
259
+ (await input({
260
+ message: "Lobu memory MCP URL:",
261
+ validate: (v) => (v ? true : "URL is required"),
262
+ }));
263
+ envSecrets.push({ envVar: "MEMORY_URL", value: lobuUrl });
149
264
  }
150
- // "none" no memory scaffold, gateway defaults to filesystem memory
151
- // Observability — OTEL tracing endpoint
152
- const otelEndpoint = await input({
153
- message: "OpenTelemetry collector endpoint? (leave empty to disable tracing)",
154
- default: "",
265
+ const otelEndpoint = await promptOrDefault({
266
+ flag: options.otelEndpoint,
267
+ useDefaults,
268
+ defaultValue: "",
269
+ prompt: () => input({
270
+ message: "OpenTelemetry collector endpoint? (leave empty to disable tracing)",
271
+ default: "",
272
+ }),
155
273
  });
156
274
  if (otelEndpoint) {
157
275
  envSecrets.push({
@@ -159,18 +277,25 @@ export async function initCommand(cwd = process.cwd(), projectNameArg) {
159
277
  value: otelEndpoint,
160
278
  });
161
279
  }
162
- // Observability Sentry error reporting
163
- const enableSentry = await confirm({
164
- message: "Help improve Lobu by sharing anonymous error reports with Sentry?",
165
- default: true,
166
- });
280
+ let enableSentry = false;
281
+ if (options.sentry === true) {
282
+ enableSentry = true;
283
+ }
284
+ else if (options.noSentry === true) {
285
+ enableSentry = false;
286
+ }
287
+ else if (!useDefaults) {
288
+ enableSentry = await confirm({
289
+ message: "Share anonymous error reports with Sentry to help improve Lobu?",
290
+ default: false,
291
+ });
292
+ }
167
293
  if (enableSentry) {
168
294
  envSecrets.push({
169
295
  envVar: "SENTRY_DSN",
170
296
  value: "https://c5910e58d1a134d64ff93a95a9c535bb@o4507291398897664.ingest.us.sentry.io/4511097466781696",
171
297
  });
172
298
  }
173
- // Compute network domains from selected policy
174
299
  let allowedDomains;
175
300
  let disallowedDomains;
176
301
  if (networkPolicy === "open") {
@@ -182,7 +307,6 @@ export async function initCommand(cwd = process.cwd(), projectNameArg) {
182
307
  disallowedDomains = "";
183
308
  }
184
309
  else {
185
- // restricted (default)
186
310
  allowedDomains = [
187
311
  "registry.npmjs.org",
188
312
  ".npmjs.org",
@@ -204,9 +328,8 @@ export async function initCommand(cwd = process.cwd(), projectNameArg) {
204
328
  };
205
329
  const spinner = ora("Creating Lobu project...").start();
206
330
  try {
207
- // Create data directory in project directory
208
331
  await mkdir(join(projectDir, "data"), { recursive: true });
209
- if (includeOwlettoMemory) {
332
+ if (includeLobuMemory) {
210
333
  await mkdir(join(projectDir, "models"), { recursive: true });
211
334
  await mkdir(join(projectDir, "data", "entities"), { recursive: true });
212
335
  await mkdir(join(projectDir, "data", "relationships"), {
@@ -216,7 +339,6 @@ export async function initCommand(cwd = process.cwd(), projectNameArg) {
216
339
  await writeFile(join(projectDir, "data", "entities", ".gitkeep"), "");
217
340
  await writeFile(join(projectDir, "data", "relationships", ".gitkeep"), "");
218
341
  }
219
- // Generate lobu.toml
220
342
  await generateLobuToml(projectDir, {
221
343
  agentName: projectName,
222
344
  allowedDomains: answers.allowedDomains,
@@ -225,9 +347,10 @@ export async function initCommand(cwd = process.cwd(), projectNameArg) {
225
347
  providerModel: selectedProvider?.providers?.[0]?.defaultModel,
226
348
  platformType: platformType || undefined,
227
349
  platformConfig: Object.keys(platformConfig).length > 0 ? platformConfig : undefined,
228
- includeOwlettoMemory,
229
- owlettoOrg: includeOwlettoMemory ? projectName : undefined,
230
- owlettoName: includeOwlettoMemory ? humanizeSlug(projectName) : undefined,
350
+ includeLobuMemory,
351
+ enableSlackPreview,
352
+ lobuOrg: includeLobuMemory ? projectName : undefined,
353
+ lobuName: includeLobuMemory ? humanizeSlug(projectName) : undefined,
231
354
  });
232
355
  const variables = {
233
356
  PROJECT_NAME: projectName,
@@ -237,40 +360,72 @@ export async function initCommand(cwd = process.cwd(), projectNameArg) {
237
360
  WORKER_ALLOWED_DOMAINS: answers.allowedDomains,
238
361
  WORKER_DISALLOWED_DOMAINS: answers.disallowedDomains,
239
362
  };
240
- // Create .env file
241
363
  await renderTemplate(".env.tmpl", variables, join(projectDir, ".env"));
242
- // Save public gateway URL if explicitly set
364
+ const envVarsToFill = new Set();
365
+ if (selectedProvider?.providers?.[0]?.envVarName) {
366
+ envVarsToFill.add(selectedProvider.providers[0].envVarName);
367
+ }
368
+ for (const value of Object.values(platformConfig)) {
369
+ const envVar = extractEnvVarRef(value);
370
+ if (envVar)
371
+ envVarsToFill.add(envVar);
372
+ }
373
+ for (const envVar of envVarsToFill) {
374
+ await setLocalEnvValue(projectDir, envVar, "");
375
+ }
243
376
  if (publicGatewayUrl) {
244
377
  await setLocalEnvValue(projectDir, "PUBLIC_GATEWAY_URL", publicGatewayUrl);
245
378
  }
246
- // Save provider API key to .env
247
379
  if (providerApiKey && selectedProvider?.providers?.[0]?.envVarName) {
248
380
  await setLocalEnvValue(projectDir, selectedProvider.providers[0].envVarName, providerApiKey);
249
381
  }
250
- // Save platform secrets to .env
251
382
  for (const secret of platformSecrets) {
252
383
  await setLocalEnvValue(projectDir, secret.envVar, secret.value);
253
384
  }
254
- // Save OAuth secrets to .env
255
385
  for (const secret of envSecrets) {
256
386
  await setLocalEnvValue(projectDir, secret.envVar, secret.value);
257
387
  }
258
- // Create .gitignore
259
388
  await renderTemplate(".gitignore.tmpl", {}, join(projectDir, ".gitignore"));
260
- // Create README
261
389
  await renderTemplate("README.md.tmpl", variables, join(projectDir, "README.md"));
262
- // Create agent directory with instruction files
263
390
  const agentDir = join(projectDir, "agents", projectName);
264
391
  await mkdir(agentDir, { recursive: true });
265
392
  await writeFile(join(agentDir, "IDENTITY.md"), `# Identity\n\nYou are ${projectName}, a helpful AI assistant.\n`);
266
393
  await writeFile(join(agentDir, "SOUL.md"), `# Instructions\n\nBe concise and helpful. Ask clarifying questions when the request is ambiguous.\n`);
267
394
  await writeFile(join(agentDir, "USER.md"), `# User Context\n\n<!-- Add user-specific preferences, timezone, environment details here -->\n`);
268
- // Create agent-specific skills directory
269
395
  await mkdir(join(agentDir, "skills"), { recursive: true });
270
396
  await writeFile(join(agentDir, "skills", ".gitkeep"), "");
271
- // Create evals directory with sample eval
272
397
  await mkdir(join(agentDir, "evals"), { recursive: true });
273
- await writeFile(join(agentDir, "evals", "ping.yaml"), `version: 1
398
+ await writeFile(join(agentDir, "evals", "ping.yaml"), DEFAULT_EVAL_YAML);
399
+ await mkdir(join(projectDir, "skills"), { recursive: true });
400
+ await writeFile(join(projectDir, "skills", ".gitkeep"), "");
401
+ await renderTemplate("AGENTS.md.tmpl", variables, join(projectDir, "AGENTS.md"));
402
+ await renderTemplate("TESTING.md.tmpl", variables, join(projectDir, "TESTING.md"));
403
+ spinner.succeed("Project created successfully!");
404
+ const gatewayUrl = `http://localhost:${gatewayPort}`;
405
+ console.log(chalk.green("\n✓ Lobu initialized!\n"));
406
+ console.log(chalk.bold("Next steps:\n"));
407
+ let n = 1;
408
+ if (!here) {
409
+ console.log(chalk.cyan(` ${n++}. cd ${projectName}`));
410
+ }
411
+ console.log(chalk.cyan(` ${n++}. Start the local stack: lobu run (uses PGlite by default)`));
412
+ console.log(chalk.dim(" Optional: set DATABASE_URL in .env to use external Postgres instead."));
413
+ if (lobuUrl) {
414
+ console.log(chalk.cyan(` ${n++}. Wire memory clients: lobu memory init`));
415
+ }
416
+ if (enableSlackPreview) {
417
+ console.log(chalk.cyan(` ${n++}. Link the project to Lobu Cloud and register it: lobu login && lobu org set <slug> && lobu apply`));
418
+ console.log(chalk.dim(" Then `lobu run` will print a short-lived Slack Preview link code."));
419
+ }
420
+ console.log(chalk.cyan(` ${n++}. API docs: ${gatewayUrl}/api/docs`));
421
+ console.log(chalk.dim("\n See README.md for layout, AGENTS.md for the agent contract.\n"));
422
+ }
423
+ catch (error) {
424
+ spinner.fail("Failed to create project");
425
+ throw error;
426
+ }
427
+ }
428
+ const DEFAULT_EVAL_YAML = `version: 1
274
429
  name: ping
275
430
  description: Agent responds to a simple greeting
276
431
  trials: 3
@@ -287,53 +442,69 @@ turns:
287
442
  - type: llm-rubric
288
443
  value: "Response is friendly and acknowledges the greeting"
289
444
  weight: 0.7
290
- `);
291
- // Create shared skills directory
292
- await mkdir(join(projectDir, "skills"), { recursive: true });
293
- await writeFile(join(projectDir, "skills", ".gitkeep"), "");
294
- // Create AGENTS.md
295
- await renderTemplate("AGENTS.md.tmpl", variables, join(projectDir, "AGENTS.md"));
296
- // Create TESTING.md
297
- await renderTemplate("TESTING.md.tmpl", variables, join(projectDir, "TESTING.md"));
298
- spinner.succeed("Project created successfully!");
299
- // Print next steps
300
- console.log(chalk.green("\n✓ Lobu initialized!\n"));
301
- console.log(chalk.bold("Next steps:\n"));
302
- console.log(chalk.cyan(" 1. Navigate to your project:"));
303
- console.log(chalk.dim(` cd ${projectName}\n`));
304
- console.log(chalk.cyan(" 2. Review your configuration:"));
305
- console.log(chalk.dim(" - lobu.toml (agents, providers, skills, network)"));
306
- console.log(chalk.dim(` - agents/${projectName}/ (IDENTITY.md, SOUL.md, USER.md, skills/)`));
307
- console.log(chalk.dim(" - skills/ (shared skills — all agents)"));
308
- if (includeOwlettoMemory) {
309
- console.log(chalk.dim(" - models/ (memory model files)"));
310
- console.log(chalk.dim(" - data/ (memory seed data)"));
311
- }
312
- console.log(chalk.dim(" - .env (secrets)"));
313
- console.log();
314
- const gatewayUrl = `http://localhost:${gatewayPort}`;
315
- console.log(chalk.cyan(" 3. Set DATABASE_URL in .env:"));
316
- console.log(chalk.dim(" Lobu connects to a user-provided Postgres. Run one yourself"));
317
- console.log(chalk.dim(" (managed instance or local: e.g. `brew services start postgresql`)\n"));
318
- if (owlettoUrl) {
319
- console.log(chalk.cyan(" Lobu memory:"));
320
- console.log(chalk.dim(` ${owlettoUrl}`));
321
- console.log(chalk.dim(" Run `lobu memory init` to configure local MCP clients.\n"));
322
- }
323
- console.log(chalk.cyan(" 4. Start the services:"));
324
- console.log(chalk.dim(" npx @lobu/cli@latest run\n"));
325
- console.log(chalk.cyan(" 5. Open the API docs:"));
326
- console.log(chalk.dim(` ${gatewayUrl}/api/docs\n`));
327
- console.log(chalk.cyan(" 6. Build with a coding agent:"));
328
- console.log(chalk.dim(" Ask Codex or Claude Code to read AGENTS.md, lobu.toml, and agents/*/{IDENTITY,SOUL,USER}.md"));
329
- console.log(chalk.dim(" Optional external skill: lobu-builder\n"));
330
- console.log(chalk.cyan(" 7. Stop the services:"));
331
- console.log(chalk.dim(" Ctrl+C in the terminal running `lobu run`\n"));
445
+ `;
446
+ function validateProjectName(value) {
447
+ if (!PROJECT_NAME_PATTERN.test(value)) {
448
+ return "Project name must be lowercase alphanumeric with hyphens only";
332
449
  }
333
- catch (error) {
334
- spinner.fail("Failed to create project");
335
- throw error;
450
+ return true;
451
+ }
452
+ function slugify(s) {
453
+ return s
454
+ .toLowerCase()
455
+ .replace(/[^a-z0-9-]+/g, "-")
456
+ .replace(/-+/g, "-")
457
+ .replace(/^-|-$/g, "");
458
+ }
459
+ async function promptOrDefault(opts) {
460
+ if (opts.flag !== undefined) {
461
+ if (opts.validate) {
462
+ const result = opts.validate(opts.flag);
463
+ if (result !== true) {
464
+ throw new Error(result);
465
+ }
466
+ }
467
+ return opts.flag;
336
468
  }
469
+ if (opts.useDefaults)
470
+ return opts.defaultValue;
471
+ return opts.prompt();
472
+ }
473
+ async function promptBooleanOrDefault(opts) {
474
+ if (opts.flag !== undefined)
475
+ return opts.flag;
476
+ if (opts.useDefaults)
477
+ return opts.defaultValue;
478
+ return opts.prompt();
479
+ }
480
+ // Placeholder env-var refs for `--yes` mode; the user fills the values into .env.
481
+ const PLATFORM_PLACEHOLDERS = {
482
+ telegram: { botToken: "$TELEGRAM_BOT_TOKEN" },
483
+ slack: {
484
+ botToken: "$SLACK_BOT_TOKEN",
485
+ signingSecret: "$SLACK_SIGNING_SECRET",
486
+ },
487
+ discord: {
488
+ botToken: "$DISCORD_BOT_TOKEN",
489
+ applicationId: "$DISCORD_APPLICATION_ID",
490
+ publicKey: "$DISCORD_PUBLIC_KEY",
491
+ },
492
+ whatsapp: {
493
+ accessToken: "$WHATSAPP_ACCESS_TOKEN",
494
+ phoneNumberId: "$WHATSAPP_PHONE_NUMBER_ID",
495
+ verifyToken: "$WHATSAPP_WEBHOOK_VERIFY_TOKEN",
496
+ appSecret: "$WHATSAPP_APP_SECRET",
497
+ },
498
+ teams: {
499
+ appId: "$TEAMS_APP_ID",
500
+ appPassword: "$TEAMS_APP_PASSWORD",
501
+ appType: "MultiTenant",
502
+ },
503
+ gchat: { credentials: "$GOOGLE_CHAT_CREDENTIALS" },
504
+ };
505
+ function extractEnvVarRef(value) {
506
+ const match = value.match(/^\$([A-Z_][A-Z0-9_]*)$/);
507
+ return match?.[1] ?? null;
337
508
  }
338
509
  function humanizeSlug(slug) {
339
510
  return slug
@@ -363,7 +534,7 @@ export async function generateLobuToml(projectDir, options) {
363
534
  lines.push(`[[agents.${id}.providers]]`, `id = "${options.providerId}"`, ...(options.providerModel ? [`model = "${options.providerModel}"`] : []), `key = "$${options.providerEnvVar}"`);
364
535
  }
365
536
  else {
366
- lines.push("# Add providers via the gateway configuration APIs or uncomment below:", `# [[agents.${id}.providers]]`, '# id = "anthropic"', '# key = "$ANTHROPIC_API_KEY"');
537
+ lines.push("# Add providers via the gateway configuration APIs or uncomment below:", `# [[agents.${id}.providers]]`, '# id = "openrouter"', '# key = "$OPENROUTER_API_KEY"');
367
538
  }
368
539
  lines.push("");
369
540
  if (options.platformType && options.platformConfig) {
@@ -377,7 +548,9 @@ export async function generateLobuToml(projectDir, options) {
377
548
  lines.push("# Chat platform (add via the gateway configuration APIs or uncomment below):", `# [[agents.${id}.platforms]]`, '# type = "telegram"', `# [agents.${id}.platforms.config]`, '# botToken = "$TELEGRAM_BOT_TOKEN"');
378
549
  }
379
550
  lines.push("", "# Local skills live in skills/<name>/SKILL.md or agents/<id>/skills/<name>/SKILL.md", `[agents.${id}.skills]`, "", "# MCP servers (add custom tool servers with optional OAuth):", `# [agents.${id}.skills.mcp.my-mcp]`, '# url = "https://my-mcp.example.com"', `# [agents.${id}.skills.mcp.my-mcp.oauth]`, '# auth_url = "https://auth.example.com/authorize"', '# token_url = "https://auth.example.com/token"', '# client_id = "$MY_MCP_CLIENT_ID"');
380
- // Network
551
+ if (options.enableSlackPreview) {
552
+ lines.push("", "# Hosted preview — `lobu run` prints a `/lobu link <code>` you redeem by", "# DMing the hosted Lobu Slack bot. The block key is the chat platform;", "# `[agents.<id>.preview.telegram]` works the same way (redeem with `/link <code>`).", `[agents.${id}.preview.slack]`, "enabled = true", 'surfaces = ["dm"]', "code_ttl_minutes = 15");
553
+ }
381
554
  lines.push("", `[agents.${id}.network]`);
382
555
  if (options.allowedDomains) {
383
556
  const domains = options.allowedDomains
@@ -389,14 +562,14 @@ export async function generateLobuToml(projectDir, options) {
389
562
  else {
390
563
  lines.push("allowed = []");
391
564
  }
392
- if (options.includeOwlettoMemory) {
393
- const org = options.owlettoOrg ?? options.agentName;
394
- const name = options.owlettoName ?? humanizeSlug(options.agentName);
395
- lines.push("", "# Project-scoped Lobu memory", `[memory.owletto]`, "enabled = true", `org = ${JSON.stringify(org)}`, `name = ${JSON.stringify(name)}`, ...(options.owlettoDescription
396
- ? [`description = ${JSON.stringify(options.owlettoDescription)}`]
565
+ if (options.includeLobuMemory) {
566
+ const org = options.lobuOrg ?? options.agentName;
567
+ const name = options.lobuName ?? humanizeSlug(options.agentName);
568
+ lines.push("", "# Project-scoped Lobu memory", `[memory]`, "enabled = true", `org = ${JSON.stringify(org)}`, `name = ${JSON.stringify(name)}`, ...(options.lobuDescription
569
+ ? [`description = ${JSON.stringify(options.lobuDescription)}`]
397
570
  : []), 'models = "./models"', 'data = "./data"');
398
571
  }
399
- lines.push(""); // trailing newline
572
+ lines.push("");
400
573
  await writeFile(join(projectDir, "lobu.toml"), lines.join("\n"));
401
574
  }
402
575
  async function getCliVersion() {