@lobu/cli 6.0.1 → 6.1.1

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 (217) 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 +2 -0
  4. package/dist/commands/_lib/apply/apply-cmd.d.ts.map +1 -1
  5. package/dist/commands/_lib/apply/apply-cmd.js +26 -0
  6. package/dist/commands/_lib/apply/apply-cmd.js.map +1 -1
  7. package/dist/commands/_lib/apply/client.d.ts +1 -1
  8. package/dist/commands/_lib/apply/client.d.ts.map +1 -1
  9. package/dist/commands/_lib/apply/desired-state.js +4 -4
  10. package/dist/commands/_lib/apply/desired-state.js.map +1 -1
  11. package/dist/commands/agent.d.ts +7 -0
  12. package/dist/commands/agent.d.ts.map +1 -1
  13. package/dist/commands/agent.js +65 -1
  14. package/dist/commands/agent.js.map +1 -1
  15. package/dist/commands/chat.d.ts +12 -9
  16. package/dist/commands/chat.d.ts.map +1 -1
  17. package/dist/commands/chat.js +117 -56
  18. package/dist/commands/chat.js.map +1 -1
  19. package/dist/commands/dev.d.ts +15 -7
  20. package/dist/commands/dev.d.ts.map +1 -1
  21. package/dist/commands/dev.js +79 -44
  22. package/dist/commands/dev.js.map +1 -1
  23. package/dist/commands/doctor.d.ts +1 -0
  24. package/dist/commands/doctor.d.ts.map +1 -1
  25. package/dist/commands/doctor.js +136 -0
  26. package/dist/commands/doctor.js.map +1 -1
  27. package/dist/commands/eval.d.ts +8 -0
  28. package/dist/commands/eval.d.ts.map +1 -1
  29. package/dist/commands/eval.js +56 -1
  30. package/dist/commands/eval.js.map +1 -1
  31. package/dist/commands/init.d.ts +20 -5
  32. package/dist/commands/init.d.ts.map +1 -1
  33. package/dist/commands/init.js +332 -183
  34. package/dist/commands/init.js.map +1 -1
  35. package/dist/commands/link.d.ts +11 -0
  36. package/dist/commands/link.d.ts.map +1 -0
  37. package/dist/commands/link.js +28 -0
  38. package/dist/commands/link.js.map +1 -0
  39. package/dist/commands/login.d.ts.map +1 -1
  40. package/dist/commands/login.js +14 -2
  41. package/dist/commands/login.js.map +1 -1
  42. package/dist/commands/memory/_lib/browser-auth-cmd.d.ts.map +1 -1
  43. package/dist/commands/memory/_lib/browser-auth-cmd.js +3 -3
  44. package/dist/commands/memory/_lib/browser-auth-cmd.js.map +1 -1
  45. package/dist/commands/memory/_lib/mcp.d.ts +2 -2
  46. package/dist/commands/memory/_lib/mcp.d.ts.map +1 -1
  47. package/dist/commands/memory/_lib/mcp.js +24 -12
  48. package/dist/commands/memory/_lib/mcp.js.map +1 -1
  49. package/dist/commands/memory/_lib/openclaw-auth.d.ts +1 -0
  50. package/dist/commands/memory/_lib/openclaw-auth.d.ts.map +1 -1
  51. package/dist/commands/memory/_lib/openclaw-auth.js +14 -3
  52. package/dist/commands/memory/_lib/openclaw-auth.js.map +1 -1
  53. package/dist/commands/memory/_lib/openclaw-cmd.js +1 -1
  54. package/dist/commands/memory/_lib/openclaw-cmd.js.map +1 -1
  55. package/dist/commands/memory/_lib/schema.d.ts +1 -1
  56. package/dist/commands/memory/_lib/schema.js +1 -1
  57. package/dist/commands/memory/_lib/seed-cmd.d.ts.map +1 -1
  58. package/dist/commands/memory/_lib/seed-cmd.js +5 -6
  59. package/dist/commands/memory/_lib/seed-cmd.js.map +1 -1
  60. package/dist/commands/memory/run.d.ts.map +1 -1
  61. package/dist/commands/memory/run.js +2 -2
  62. package/dist/commands/memory/run.js.map +1 -1
  63. package/dist/commands/platforms/platform-prompts.d.ts +0 -1
  64. package/dist/commands/platforms/platform-prompts.d.ts.map +1 -1
  65. package/dist/commands/platforms/platform-prompts.js +54 -8
  66. package/dist/commands/platforms/platform-prompts.js.map +1 -1
  67. package/dist/commands/telemetry.d.ts +10 -0
  68. package/dist/commands/telemetry.d.ts.map +1 -0
  69. package/dist/commands/telemetry.js +68 -0
  70. package/dist/commands/telemetry.js.map +1 -0
  71. package/dist/commands/whoami.d.ts.map +1 -1
  72. package/dist/commands/whoami.js +1 -1
  73. package/dist/commands/whoami.js.map +1 -1
  74. package/dist/connectors/README.md +534 -0
  75. package/dist/connectors/__tests__/browser-scraper-utils.test.ts +186 -0
  76. package/dist/connectors/browser-scraper-utils.ts +214 -0
  77. package/dist/connectors/capterra.ts +273 -0
  78. package/dist/connectors/g2.ts +286 -0
  79. package/dist/connectors/github.ts +1553 -0
  80. package/dist/connectors/glassdoor.ts +291 -0
  81. package/dist/connectors/gmaps.ts +197 -0
  82. package/dist/connectors/google_calendar.ts +631 -0
  83. package/dist/connectors/google_gmail.ts +751 -0
  84. package/dist/connectors/google_photos.ts +776 -0
  85. package/dist/connectors/google_play.ts +342 -0
  86. package/dist/connectors/hackernews.ts +471 -0
  87. package/dist/connectors/index.ts +23 -0
  88. package/dist/connectors/ios_appstore.ts +226 -0
  89. package/dist/connectors/linkedin.ts +471 -0
  90. package/dist/connectors/microsoft_outlook.ts +410 -0
  91. package/dist/connectors/producthunt.ts +471 -0
  92. package/dist/connectors/reddit.ts +600 -0
  93. package/dist/connectors/rss.ts +448 -0
  94. package/dist/connectors/spotify.ts +590 -0
  95. package/dist/connectors/trustpilot.ts +199 -0
  96. package/dist/connectors/website.ts +629 -0
  97. package/dist/connectors/whatsapp.ts +1073 -0
  98. package/dist/connectors/x.ts +526 -0
  99. package/dist/connectors/youtube.ts +666 -0
  100. package/dist/db/migrations/00000000000000_baseline.sql +4867 -0
  101. package/dist/db/migrations/20260405193000_add_mcp_sessions.sql +33 -0
  102. package/dist/db/migrations/20260408120000_remove_system_connectors.sql +48 -0
  103. package/dist/db/migrations/20260408120001_optional_compiled_code.sql +6 -0
  104. package/dist/db/migrations/20260409110000_add_active_watcher_run_index.sql +9 -0
  105. package/dist/db/migrations/20260409130000_connector_default_config.sql +5 -0
  106. package/dist/db/migrations/20260410120000_add_agent_secrets.sql +25 -0
  107. package/dist/db/migrations/20260413170000_add_watcher_group_id.sql +67 -0
  108. package/dist/db/migrations/20260416120000_add_entity_wa_jid_index.sql +14 -0
  109. package/dist/db/migrations/20260417100000_add_entity_identities.sql +77 -0
  110. package/dist/db/migrations/20260418100000_add_auth_runs.sql +83 -0
  111. package/dist/db/migrations/20260418110000_add_runs_created_by_user.sql +18 -0
  112. package/dist/db/migrations/20260419120000_add_event_identity_indexes.sql +56 -0
  113. package/dist/db/migrations/20260420120000_extend_reserved_org_slugs.sql +56 -0
  114. package/dist/db/migrations/20260424030000_add_watcher_run_correlation.sql +52 -0
  115. package/dist/db/migrations/20260424130000_relax_events_client_id_fk.sql +47 -0
  116. package/dist/db/migrations/20260425100000_normalize_watcher_feedback.sql +91 -0
  117. package/dist/db/migrations/20260425120000_add_run_diagnostics.sql +20 -0
  118. package/dist/db/migrations/20260425130000_add_repair_agent_plumbing.sql +46 -0
  119. package/dist/db/migrations/20260426120000_entities_entity_type_fk.sql +101 -0
  120. package/dist/db/migrations/20260426130000_db_integrity_cleanup.sql +104 -0
  121. package/dist/db/migrations/20260426130001_db_integrity_cleanup_concurrent.sql +187 -0
  122. package/dist/db/migrations/20260427133000_events_created_by_nullable.sql +74 -0
  123. package/dist/db/migrations/20260427140000_identity_engine_indexes.sql +140 -0
  124. package/dist/db/migrations/20260427150000_drop_events_source_id.sql +177 -0
  125. package/dist/db/migrations/20260427160000_drop_dead_schema.sql +76 -0
  126. package/dist/db/migrations/20260427170000_market_founder_to_member.sql +364 -0
  127. package/dist/db/migrations/20260428040000_cascade_events_watchers_org_fk.sql +66 -0
  128. package/dist/db/migrations/20260428050000_add_runs_approved_input.sql +9 -0
  129. package/dist/db/migrations/20260429010000_auth_profile_tenant_scoped_fk.sql +79 -0
  130. package/dist/db/migrations/20260429060000_extend_runs_for_lobu_queue.sql +108 -0
  131. package/dist/db/migrations/20260429120000_agent_changed_notify.sql +97 -0
  132. package/dist/db/migrations/20260429120100_user_auth_profiles_and_model_prefs.sql +36 -0
  133. package/dist/db/migrations/20260429120200_fix_notify_old_keys.sql +130 -0
  134. package/dist/db/migrations/20260429130000_oauth_states_cli_sessions_rate_limits.sql +83 -0
  135. package/dist/db/migrations/20260429140000_phase8_grants_chat_connections_mcp_sessions.sql +84 -0
  136. package/dist/db/migrations/20260429140100_runs_priority_expires_at_retry_delay.sql +44 -0
  137. package/dist/db/migrations/20260429180000_drop_invalidatable_cache_triggers.sql +25 -0
  138. package/dist/db/migrations/20260430005614_agents_apply_fields.sql +21 -0
  139. package/dist/db/migrations/20260430022231_fix_connection_config_encryption.sql +69 -0
  140. package/dist/db/migrations/20260430151215_add_task_run_type.sql +77 -0
  141. package/dist/db/migrations/20260501000000_drop_cli_sessions.sql +27 -0
  142. package/dist/db/migrations/20260501133000_lobu_memory_mcp_id.sql +117 -0
  143. package/dist/db/migrations/20260502000000_drop_chat_connections.sql +60 -0
  144. package/dist/db/migrations/20260503000000_agent_secrets_org_scope.sql +56 -0
  145. package/dist/db/migrations/20260504000000_flatten_agents_drop_sandbox_model.sql +48 -0
  146. package/dist/index.d.ts.map +1 -1
  147. package/dist/index.js +147 -23
  148. package/dist/index.js.map +1 -1
  149. package/dist/internal/api-client.d.ts +4 -8
  150. package/dist/internal/api-client.d.ts.map +1 -1
  151. package/dist/internal/api-client.js +1 -1
  152. package/dist/internal/api-client.js.map +1 -1
  153. package/dist/internal/context.js +2 -2
  154. package/dist/internal/context.js.map +1 -1
  155. package/dist/internal/credentials.d.ts.map +1 -1
  156. package/dist/internal/credentials.js +6 -1
  157. package/dist/internal/credentials.js.map +1 -1
  158. package/dist/internal/index.d.ts +2 -3
  159. package/dist/internal/index.d.ts.map +1 -1
  160. package/dist/internal/index.js +2 -2
  161. package/dist/internal/index.js.map +1 -1
  162. package/dist/internal/oauth.d.ts +6 -5
  163. package/dist/internal/oauth.d.ts.map +1 -1
  164. package/dist/internal/oauth.js +2 -2
  165. package/dist/internal/project-link.d.ts +10 -0
  166. package/dist/internal/project-link.d.ts.map +1 -0
  167. package/dist/internal/project-link.js +48 -0
  168. package/dist/internal/project-link.js.map +1 -0
  169. package/dist/providers.json +2 -2
  170. package/dist/server.bundle.mjs +3090 -4321
  171. package/dist/start-local.bundle.mjs +71481 -0
  172. package/dist/templates/README.md.tmpl +10 -11
  173. package/package.json +14 -12
  174. package/dist/__tests__/chat.integration.test.d.ts +0 -2
  175. package/dist/__tests__/chat.integration.test.d.ts.map +0 -1
  176. package/dist/__tests__/chat.integration.test.js +0 -337
  177. package/dist/__tests__/chat.integration.test.js.map +0 -1
  178. package/dist/__tests__/dev.test.d.ts +0 -2
  179. package/dist/__tests__/dev.test.d.ts.map +0 -1
  180. package/dist/__tests__/dev.test.js +0 -25
  181. package/dist/__tests__/dev.test.js.map +0 -1
  182. package/dist/__tests__/init-memory.test.d.ts +0 -2
  183. package/dist/__tests__/init-memory.test.d.ts.map +0 -1
  184. package/dist/__tests__/init-memory.test.js +0 -45
  185. package/dist/__tests__/init-memory.test.js.map +0 -1
  186. package/dist/__tests__/token.test.d.ts +0 -2
  187. package/dist/__tests__/token.test.d.ts.map +0 -1
  188. package/dist/__tests__/token.test.js +0 -52
  189. package/dist/__tests__/token.test.js.map +0 -1
  190. package/dist/commands/_lib/apply/__tests__/client.test.d.ts +0 -2
  191. package/dist/commands/_lib/apply/__tests__/client.test.d.ts.map +0 -1
  192. package/dist/commands/_lib/apply/__tests__/client.test.js +0 -23
  193. package/dist/commands/_lib/apply/__tests__/client.test.js.map +0 -1
  194. package/dist/commands/_lib/apply/__tests__/desired-state.test.d.ts +0 -2
  195. package/dist/commands/_lib/apply/__tests__/desired-state.test.d.ts.map +0 -1
  196. package/dist/commands/_lib/apply/__tests__/desired-state.test.js +0 -140
  197. package/dist/commands/_lib/apply/__tests__/desired-state.test.js.map +0 -1
  198. package/dist/commands/_lib/apply/__tests__/diff.test.d.ts +0 -2
  199. package/dist/commands/_lib/apply/__tests__/diff.test.d.ts.map +0 -1
  200. package/dist/commands/_lib/apply/__tests__/diff.test.js +0 -378
  201. package/dist/commands/_lib/apply/__tests__/diff.test.js.map +0 -1
  202. package/dist/commands/apply.d.ts +0 -3
  203. package/dist/commands/apply.d.ts.map +0 -1
  204. package/dist/commands/apply.js +0 -5
  205. package/dist/commands/apply.js.map +0 -1
  206. package/dist/commands/memory/_lib/openclaw-auth.test.d.ts +0 -2
  207. package/dist/commands/memory/_lib/openclaw-auth.test.d.ts.map +0 -1
  208. package/dist/commands/memory/_lib/openclaw-auth.test.js +0 -9
  209. package/dist/commands/memory/_lib/openclaw-auth.test.js.map +0 -1
  210. package/dist/internal/__tests__/api-client.test.d.ts +0 -2
  211. package/dist/internal/__tests__/api-client.test.d.ts.map +0 -1
  212. package/dist/internal/__tests__/api-client.test.js +0 -95
  213. package/dist/internal/__tests__/api-client.test.js.map +0 -1
  214. package/dist/internal/__tests__/context.test.d.ts +0 -2
  215. package/dist/internal/__tests__/context.test.d.ts.map +0 -1
  216. package/dist/internal/__tests__/context.test.js +0 -77
  217. 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,72 @@ 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: "",
123
- });
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",
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
+ }),
136
210
  });
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 memoryChoice = (await promptOrDefault({
225
+ flag: options.memory,
226
+ useDefaults,
227
+ defaultValue: "none",
228
+ validate: (v) => MEMORY_CHOICES.includes(v)
229
+ ? true
230
+ : `Must be one of: ${MEMORY_CHOICES.join(", ")}`,
231
+ prompt: () => select({
232
+ message: "Memory:",
233
+ choices: [
234
+ { name: "None (filesystem memory)", value: "none" },
235
+ { name: "Lobu Cloud (app.lobu.ai)", value: "lobu-cloud" },
236
+ { name: "Custom Lobu memory URL", value: "lobu-custom" },
237
+ ],
238
+ default: "none",
239
+ }),
240
+ }));
137
241
  const envSecrets = [];
138
- const includeOwlettoMemory = memoryChoice !== "none";
139
- let owlettoUrl = "";
140
- if (memoryChoice === "owletto-cloud") {
141
- owlettoUrl = DEFAULT_OWLETTO_MCP_URL;
242
+ const includeLobuMemory = memoryChoice !== "none";
243
+ let lobuUrl = "";
244
+ if (memoryChoice === "lobu-cloud") {
245
+ lobuUrl = DEFAULT_LOBU_MCP_URL;
142
246
  }
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 });
247
+ else if (memoryChoice === "lobu-custom") {
248
+ lobuUrl =
249
+ options.memoryUrl ??
250
+ (await input({
251
+ message: "Lobu memory MCP URL:",
252
+ validate: (v) => (v ? true : "URL is required"),
253
+ }));
254
+ envSecrets.push({ envVar: "MEMORY_URL", value: lobuUrl });
149
255
  }
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: "",
256
+ const otelEndpoint = await promptOrDefault({
257
+ flag: options.otelEndpoint,
258
+ useDefaults,
259
+ defaultValue: "",
260
+ prompt: () => input({
261
+ message: "OpenTelemetry collector endpoint? (leave empty to disable tracing)",
262
+ default: "",
263
+ }),
155
264
  });
156
265
  if (otelEndpoint) {
157
266
  envSecrets.push({
@@ -159,18 +268,25 @@ export async function initCommand(cwd = process.cwd(), projectNameArg) {
159
268
  value: otelEndpoint,
160
269
  });
161
270
  }
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
- });
271
+ let enableSentry = false;
272
+ if (options.sentry === true) {
273
+ enableSentry = true;
274
+ }
275
+ else if (options.noSentry === true) {
276
+ enableSentry = false;
277
+ }
278
+ else if (!useDefaults) {
279
+ enableSentry = await confirm({
280
+ message: "Share anonymous error reports with Sentry to help improve Lobu?",
281
+ default: false,
282
+ });
283
+ }
167
284
  if (enableSentry) {
168
285
  envSecrets.push({
169
286
  envVar: "SENTRY_DSN",
170
287
  value: "https://c5910e58d1a134d64ff93a95a9c535bb@o4507291398897664.ingest.us.sentry.io/4511097466781696",
171
288
  });
172
289
  }
173
- // Compute network domains from selected policy
174
290
  let allowedDomains;
175
291
  let disallowedDomains;
176
292
  if (networkPolicy === "open") {
@@ -182,7 +298,6 @@ export async function initCommand(cwd = process.cwd(), projectNameArg) {
182
298
  disallowedDomains = "";
183
299
  }
184
300
  else {
185
- // restricted (default)
186
301
  allowedDomains = [
187
302
  "registry.npmjs.org",
188
303
  ".npmjs.org",
@@ -204,9 +319,8 @@ export async function initCommand(cwd = process.cwd(), projectNameArg) {
204
319
  };
205
320
  const spinner = ora("Creating Lobu project...").start();
206
321
  try {
207
- // Create data directory in project directory
208
322
  await mkdir(join(projectDir, "data"), { recursive: true });
209
- if (includeOwlettoMemory) {
323
+ if (includeLobuMemory) {
210
324
  await mkdir(join(projectDir, "models"), { recursive: true });
211
325
  await mkdir(join(projectDir, "data", "entities"), { recursive: true });
212
326
  await mkdir(join(projectDir, "data", "relationships"), {
@@ -216,7 +330,6 @@ export async function initCommand(cwd = process.cwd(), projectNameArg) {
216
330
  await writeFile(join(projectDir, "data", "entities", ".gitkeep"), "");
217
331
  await writeFile(join(projectDir, "data", "relationships", ".gitkeep"), "");
218
332
  }
219
- // Generate lobu.toml
220
333
  await generateLobuToml(projectDir, {
221
334
  agentName: projectName,
222
335
  allowedDomains: answers.allowedDomains,
@@ -225,9 +338,9 @@ export async function initCommand(cwd = process.cwd(), projectNameArg) {
225
338
  providerModel: selectedProvider?.providers?.[0]?.defaultModel,
226
339
  platformType: platformType || undefined,
227
340
  platformConfig: Object.keys(platformConfig).length > 0 ? platformConfig : undefined,
228
- includeOwlettoMemory,
229
- owlettoOrg: includeOwlettoMemory ? projectName : undefined,
230
- owlettoName: includeOwlettoMemory ? humanizeSlug(projectName) : undefined,
341
+ includeLobuMemory,
342
+ lobuOrg: includeLobuMemory ? projectName : undefined,
343
+ lobuName: includeLobuMemory ? humanizeSlug(projectName) : undefined,
231
344
  });
232
345
  const variables = {
233
346
  PROJECT_NAME: projectName,
@@ -237,40 +350,68 @@ export async function initCommand(cwd = process.cwd(), projectNameArg) {
237
350
  WORKER_ALLOWED_DOMAINS: answers.allowedDomains,
238
351
  WORKER_DISALLOWED_DOMAINS: answers.disallowedDomains,
239
352
  };
240
- // Create .env file
241
353
  await renderTemplate(".env.tmpl", variables, join(projectDir, ".env"));
242
- // Save public gateway URL if explicitly set
354
+ const envVarsToFill = new Set();
355
+ if (selectedProvider?.providers?.[0]?.envVarName) {
356
+ envVarsToFill.add(selectedProvider.providers[0].envVarName);
357
+ }
358
+ for (const value of Object.values(platformConfig)) {
359
+ const envVar = extractEnvVarRef(value);
360
+ if (envVar)
361
+ envVarsToFill.add(envVar);
362
+ }
363
+ for (const envVar of envVarsToFill) {
364
+ await setLocalEnvValue(projectDir, envVar, "");
365
+ }
243
366
  if (publicGatewayUrl) {
244
367
  await setLocalEnvValue(projectDir, "PUBLIC_GATEWAY_URL", publicGatewayUrl);
245
368
  }
246
- // Save provider API key to .env
247
369
  if (providerApiKey && selectedProvider?.providers?.[0]?.envVarName) {
248
370
  await setLocalEnvValue(projectDir, selectedProvider.providers[0].envVarName, providerApiKey);
249
371
  }
250
- // Save platform secrets to .env
251
372
  for (const secret of platformSecrets) {
252
373
  await setLocalEnvValue(projectDir, secret.envVar, secret.value);
253
374
  }
254
- // Save OAuth secrets to .env
255
375
  for (const secret of envSecrets) {
256
376
  await setLocalEnvValue(projectDir, secret.envVar, secret.value);
257
377
  }
258
- // Create .gitignore
259
378
  await renderTemplate(".gitignore.tmpl", {}, join(projectDir, ".gitignore"));
260
- // Create README
261
379
  await renderTemplate("README.md.tmpl", variables, join(projectDir, "README.md"));
262
- // Create agent directory with instruction files
263
380
  const agentDir = join(projectDir, "agents", projectName);
264
381
  await mkdir(agentDir, { recursive: true });
265
382
  await writeFile(join(agentDir, "IDENTITY.md"), `# Identity\n\nYou are ${projectName}, a helpful AI assistant.\n`);
266
383
  await writeFile(join(agentDir, "SOUL.md"), `# Instructions\n\nBe concise and helpful. Ask clarifying questions when the request is ambiguous.\n`);
267
384
  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
385
  await mkdir(join(agentDir, "skills"), { recursive: true });
270
386
  await writeFile(join(agentDir, "skills", ".gitkeep"), "");
271
- // Create evals directory with sample eval
272
387
  await mkdir(join(agentDir, "evals"), { recursive: true });
273
- await writeFile(join(agentDir, "evals", "ping.yaml"), `version: 1
388
+ await writeFile(join(agentDir, "evals", "ping.yaml"), DEFAULT_EVAL_YAML);
389
+ await mkdir(join(projectDir, "skills"), { recursive: true });
390
+ await writeFile(join(projectDir, "skills", ".gitkeep"), "");
391
+ await renderTemplate("AGENTS.md.tmpl", variables, join(projectDir, "AGENTS.md"));
392
+ await renderTemplate("TESTING.md.tmpl", variables, join(projectDir, "TESTING.md"));
393
+ spinner.succeed("Project created successfully!");
394
+ const gatewayUrl = `http://localhost:${gatewayPort}`;
395
+ console.log(chalk.green("\n✓ Lobu initialized!\n"));
396
+ console.log(chalk.bold("Next steps:\n"));
397
+ let n = 1;
398
+ if (!here) {
399
+ console.log(chalk.cyan(` ${n++}. cd ${projectName}`));
400
+ }
401
+ console.log(chalk.cyan(` ${n++}. Start the local stack: lobu run (uses PGlite by default)`));
402
+ console.log(chalk.dim(" Optional: set DATABASE_URL in .env to use external Postgres instead."));
403
+ if (lobuUrl) {
404
+ console.log(chalk.cyan(` ${n++}. Wire memory clients: lobu memory init`));
405
+ }
406
+ console.log(chalk.cyan(` ${n++}. API docs: ${gatewayUrl}/api/docs`));
407
+ console.log(chalk.dim("\n See README.md for layout, AGENTS.md for the agent contract.\n"));
408
+ }
409
+ catch (error) {
410
+ spinner.fail("Failed to create project");
411
+ throw error;
412
+ }
413
+ }
414
+ const DEFAULT_EVAL_YAML = `version: 1
274
415
  name: ping
275
416
  description: Agent responds to a simple greeting
276
417
  trials: 3
@@ -287,53 +428,62 @@ turns:
287
428
  - type: llm-rubric
288
429
  value: "Response is friendly and acknowledges the greeting"
289
430
  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"));
431
+ `;
432
+ function validateProjectName(value) {
433
+ if (!PROJECT_NAME_PATTERN.test(value)) {
434
+ return "Project name must be lowercase alphanumeric with hyphens only";
332
435
  }
333
- catch (error) {
334
- spinner.fail("Failed to create project");
335
- throw error;
436
+ return true;
437
+ }
438
+ function slugify(s) {
439
+ return s
440
+ .toLowerCase()
441
+ .replace(/[^a-z0-9-]+/g, "-")
442
+ .replace(/-+/g, "-")
443
+ .replace(/^-|-$/g, "");
444
+ }
445
+ async function promptOrDefault(opts) {
446
+ if (opts.flag !== undefined) {
447
+ if (opts.validate) {
448
+ const result = opts.validate(opts.flag);
449
+ if (result !== true) {
450
+ throw new Error(result);
451
+ }
452
+ }
453
+ return opts.flag;
336
454
  }
455
+ if (opts.useDefaults)
456
+ return opts.defaultValue;
457
+ return opts.prompt();
458
+ }
459
+ // Placeholder env-var refs for `--yes` mode; the user fills the values into .env.
460
+ const PLATFORM_PLACEHOLDERS = {
461
+ telegram: { botToken: "$TELEGRAM_BOT_TOKEN" },
462
+ slack: {
463
+ botToken: "$SLACK_BOT_TOKEN",
464
+ signingSecret: "$SLACK_SIGNING_SECRET",
465
+ },
466
+ discord: {
467
+ botToken: "$DISCORD_BOT_TOKEN",
468
+ applicationId: "$DISCORD_APPLICATION_ID",
469
+ publicKey: "$DISCORD_PUBLIC_KEY",
470
+ },
471
+ whatsapp: {
472
+ accessToken: "$WHATSAPP_ACCESS_TOKEN",
473
+ phoneNumberId: "$WHATSAPP_PHONE_NUMBER_ID",
474
+ verifyToken: "$WHATSAPP_WEBHOOK_VERIFY_TOKEN",
475
+ appSecret: "$WHATSAPP_APP_SECRET",
476
+ },
477
+ teams: {
478
+ appId: "$TEAMS_APP_ID",
479
+ appPassword: "$TEAMS_APP_PASSWORD",
480
+ appType: "MultiTenant",
481
+ },
482
+ gchat: { credentials: "$GOOGLE_CHAT_CREDENTIALS" },
483
+ };
484
+ function extractEnvVarRef(value) {
485
+ const match = value.match(/^\$([A-Z_][A-Z0-9_]*)$/);
486
+ return match?.[1] ?? null;
337
487
  }
338
488
  function humanizeSlug(slug) {
339
489
  return slug
@@ -363,7 +513,7 @@ export async function generateLobuToml(projectDir, options) {
363
513
  lines.push(`[[agents.${id}.providers]]`, `id = "${options.providerId}"`, ...(options.providerModel ? [`model = "${options.providerModel}"`] : []), `key = "$${options.providerEnvVar}"`);
364
514
  }
365
515
  else {
366
- lines.push("# Add providers via the gateway configuration APIs or uncomment below:", `# [[agents.${id}.providers]]`, '# id = "anthropic"', '# key = "$ANTHROPIC_API_KEY"');
516
+ lines.push("# Add providers via the gateway configuration APIs or uncomment below:", `# [[agents.${id}.providers]]`, '# id = "openrouter"', '# key = "$OPENROUTER_API_KEY"');
367
517
  }
368
518
  lines.push("");
369
519
  if (options.platformType && options.platformConfig) {
@@ -377,7 +527,6 @@ export async function generateLobuToml(projectDir, options) {
377
527
  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
528
  }
379
529
  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
381
530
  lines.push("", `[agents.${id}.network]`);
382
531
  if (options.allowedDomains) {
383
532
  const domains = options.allowedDomains
@@ -389,14 +538,14 @@ export async function generateLobuToml(projectDir, options) {
389
538
  else {
390
539
  lines.push("allowed = []");
391
540
  }
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)}`]
541
+ if (options.includeLobuMemory) {
542
+ const org = options.lobuOrg ?? options.agentName;
543
+ const name = options.lobuName ?? humanizeSlug(options.agentName);
544
+ lines.push("", "# Project-scoped Lobu memory", `[memory]`, "enabled = true", `org = ${JSON.stringify(org)}`, `name = ${JSON.stringify(name)}`, ...(options.lobuDescription
545
+ ? [`description = ${JSON.stringify(options.lobuDescription)}`]
397
546
  : []), 'models = "./models"', 'data = "./data"');
398
547
  }
399
- lines.push(""); // trailing newline
548
+ lines.push("");
400
549
  await writeFile(join(projectDir, "lobu.toml"), lines.join("\n"));
401
550
  }
402
551
  async function getCliVersion() {