profclaw 0.0.1-beta.3 → 2.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 (156) hide show
  1. package/CHANGELOG.md +21 -38
  2. package/CONTRIBUTING.md +3 -5
  3. package/README.md +128 -63
  4. package/dist/chat/execution/tools/index.d.ts +1 -1
  5. package/dist/chat/execution/tools/index.js +41 -2
  6. package/dist/chat/providers/slack/index.d.ts +2 -2
  7. package/dist/cli/commands/models.js +39 -26
  8. package/dist/cli/commands/onboard.js +64 -79
  9. package/dist/cli/commands/setup.js +293 -90
  10. package/dist/cli/index.js +0 -6
  11. package/dist/cli/providers.d.ts +15 -0
  12. package/dist/cli/providers.js +41 -0
  13. package/dist/core/deployment.js +2 -0
  14. package/dist/providers/ai-sdk.js +26 -6
  15. package/dist/routes/channels.d.ts +12 -0
  16. package/dist/routes/channels.js +166 -0
  17. package/dist/server/route-loader.js +1 -0
  18. package/dist/server.js +115 -109
  19. package/package.json +1 -1
  20. package/skills/git-workflow/SKILL.md +3 -3
  21. package/ui/dist/assets/{ActivityView-DIPQSJ_d.js → ActivityView-BeshZXmE.js} +1 -1
  22. package/ui/dist/assets/{AgentList-CqyaYrcq.js → AgentList-QVHeldPi.js} +1 -1
  23. package/ui/dist/assets/{AnalyticsDashboard-C-mjuGHT.js → AnalyticsDashboard-C9R97dXE.js} +1 -1
  24. package/ui/dist/assets/{AreaChart-CfYWmbtr.js → AreaChart-B73gaiVQ.js} +1 -1
  25. package/ui/dist/assets/{CartesianChart-BaQ3It0c.js → CartesianChart-CXYL2R-A.js} +1 -1
  26. package/ui/dist/assets/{ChatView-DxxQy6pl.js → ChatView-qjZErlrp.js} +1 -1
  27. package/ui/dist/assets/{CostsDashboard-CDBBcGkg.js → CostsDashboard-O4DCLy1i.js} +1 -1
  28. package/ui/dist/assets/{EstimateSelect-CDjf5qKl.js → EstimateSelect-1hQf4Osk.js} +1 -1
  29. package/ui/dist/assets/{FloatingChatbot-CZHjvJIe.js → FloatingChatbot-BZBzrJxd.js} +1 -1
  30. package/ui/dist/assets/{InviteCodeManagement-DTm-IPTm.js → InviteCodeManagement-LEoGqYeA.js} +1 -1
  31. package/ui/dist/assets/{ProjectDetail-DlYUcOss.js → ProjectDetail--gPfvATB.js} +1 -1
  32. package/ui/dist/assets/{ProjectIcon-BMRpnM04.js → ProjectIcon-Chd_ofsw.js} +1 -1
  33. package/ui/dist/assets/{ProjectList-D85TN0i0.js → ProjectList-BvUfVB6c.js} +1 -1
  34. package/ui/dist/assets/{Settings-CgPqzUvj.js → Settings-1kaf7VHE.js} +1 -1
  35. package/ui/dist/assets/{StatusIndicator-yig0uZEA.js → StatusIndicator-pRfkNcdV.js} +1 -1
  36. package/ui/dist/assets/{SummaryDetail-CwLAfHgJ.js → SummaryDetail-DV3AmONk.js} +1 -1
  37. package/ui/dist/assets/{SummaryList-BvS5Idnu.js → SummaryList-B6kXiTxL.js} +1 -1
  38. package/ui/dist/assets/{TaskDetail-zarC1fOt.js → TaskDetail-CV6CzbOS.js} +1 -1
  39. package/ui/dist/assets/{TaskList-CyJq3ANE.js → TaskList-DL7brQKd.js} +1 -1
  40. package/ui/dist/assets/{TicketBoard-BlWr3-iH.js → TicketBoard-CrkkFj7C.js} +1 -1
  41. package/ui/dist/assets/{TicketDetail-DmqWKj2W.js → TicketDetail-hnnYclpD.js} +1 -1
  42. package/ui/dist/assets/{TicketList-Scj4pMWa.js → TicketList-CsDAyQCL.js} +1 -1
  43. package/ui/dist/assets/{UserManagement-BH7uG7dw.js → UserManagement-BGFQyTSr.js} +1 -1
  44. package/ui/dist/assets/{ViewSwitcher-3HzZUdWn.js → ViewSwitcher-CnL0OqA8.js} +1 -1
  45. package/ui/dist/assets/{alert-dialog-DWPb2IfQ.js → alert-dialog-SKM6Gjz4.js} +1 -1
  46. package/ui/dist/assets/{archive-CGck7gxR.js → archive-BsLa7brF.js} +1 -1
  47. package/ui/dist/assets/{badge-DEmc3Z_l.js → badge-CVHH1a-v.js} +1 -1
  48. package/ui/dist/assets/{brain-CfFdLVqb.js → brain-6o_RHVpe.js} +1 -1
  49. package/ui/dist/assets/{bug-DqSGPUUE.js → bug-CmZI7jqM.js} +1 -1
  50. package/ui/dist/assets/{calendar-BuZ4zbPW.js → calendar-Bf0t2B82.js} +1 -1
  51. package/ui/dist/assets/{chevron-up-DcJ4LL_N.js → chevron-up-CoUsOvZj.js} +1 -1
  52. package/ui/dist/assets/{circle-x-C78PNZjF.js → circle-x-Dgw11LgT.js} +1 -1
  53. package/ui/dist/assets/{constants-DhDjZe4P.js → constants-Q5Zivl-a.js} +1 -1
  54. package/ui/dist/assets/{cpu-Ck6AoG8c.js → cpu-DiPMQICV.js} +1 -1
  55. package/ui/dist/assets/{database-CA2w8Lvt.js → database-BwTfupDi.js} +1 -1
  56. package/ui/dist/assets/{dialog-BOdGWhgE.js → dialog-BUqN9Psj.js} +1 -1
  57. package/ui/dist/assets/{ellipsis-FhHKRbfl.js → ellipsis-UJx4Xnf6.js} +1 -1
  58. package/ui/dist/assets/{ellipsis-vertical-CO0UqE4-.js → ellipsis-vertical-DoULYEKn.js} +1 -1
  59. package/ui/dist/assets/{file-code-ChnhYYj1.js → file-code-B2UrJLwi.js} +1 -1
  60. package/ui/dist/assets/{folder-open-CHwA1-ve.js → folder-open-BJi577JH.js} +1 -1
  61. package/ui/dist/assets/{funnel-ByEjRFNB.js → funnel-PkaD_HSR.js} +1 -1
  62. package/ui/dist/assets/{git-branch-GTOPQgU4.js → git-branch-C_K30am0.js} +1 -1
  63. package/ui/dist/assets/{git-commit-horizontal-F5M84Kut.js → git-commit-horizontal-IT6ecxWQ.js} +1 -1
  64. package/ui/dist/assets/{globe-CQWfr7hT.js → globe-f-jjCa_g.js} +1 -1
  65. package/ui/dist/assets/{hash-nKC9gtLg.js → hash-DNEv8Elo.js} +1 -1
  66. package/ui/dist/assets/{history-DfFSA9xJ.js → history-BQSBo_1Q.js} +1 -1
  67. package/ui/dist/assets/{index-DHGvP9mp.js → index-BUmnwPQr.js} +1 -1
  68. package/ui/dist/assets/{index-Z4-RtZcs.js → index-BfF5G4Cw.js} +1 -1
  69. package/ui/dist/assets/{index-DeI8lKRR.js → index-C8N375c2.js} +1 -1
  70. package/ui/dist/assets/index-CuxnqVVz.css +1 -0
  71. package/ui/dist/assets/{index-Ca3vk7Gq.js → index-CzMM3QOk.js} +1 -1
  72. package/ui/dist/assets/{index--JkIxVXb.js → index-lJlNPFVJ.js} +3 -3
  73. package/ui/dist/assets/{index-a4Fxq0Hb.js → index-nS3EErDg.js} +1 -1
  74. package/ui/dist/assets/{layers-DFeLExSa.js → layers-jFqVrWMD.js} +1 -1
  75. package/ui/dist/assets/{layout-dashboard-WmgjMhWF.js → layout-dashboard-CcKt1hXw.js} +1 -1
  76. package/ui/dist/assets/{lightbulb-Bo8hEr7B.js → lightbulb-Jhp7FvbU.js} +1 -1
  77. package/ui/dist/assets/{link-2-YqrpQXBH.js → link-2-DdO7ostF.js} +1 -1
  78. package/ui/dist/assets/{markdown-renderer-Croje6Xe.js → markdown-renderer-n1ToC4XQ.js} +1 -1
  79. package/ui/dist/assets/{message-square-CD0H5hot.js → message-square-CBgH65X7.js} +1 -1
  80. package/ui/dist/assets/{pencil-DpFnj5dn.js → pencil-CYXCEPQh.js} +1 -1
  81. package/ui/dist/assets/{play-DVgea_jT.js → play-d1ONaH0-.js} +1 -1
  82. package/ui/dist/assets/{popover-JXecfWG5.js → popover-CrmZgoWu.js} +1 -1
  83. package/ui/dist/assets/{progress-C1ghf1Fu.js → progress-uv4SPkL5.js} +1 -1
  84. package/ui/dist/assets/{rocket-BFxPYGx9.js → rocket-DhvUetsk.js} +1 -1
  85. package/ui/dist/assets/{select-MADP8m6Q.js → select-CeQ8AHGy.js} +1 -1
  86. package/ui/dist/assets/{send-eYnW_7n-.js → send-P0r5D7td.js} +1 -1
  87. package/ui/dist/assets/{server-CPMaysH8.js → server-CVHTZB72.js} +1 -1
  88. package/ui/dist/assets/{settings-2-scXUOuE_.js → settings-2-ocj5uHGx.js} +1 -1
  89. package/ui/dist/assets/{sheet-r6NSOxi-.js → sheet-DuQExSho.js} +1 -1
  90. package/ui/dist/assets/{shield-check-PEr7nmVw.js → shield-check-DfVdE34l.js} +1 -1
  91. package/ui/dist/assets/{shield-off-CLtXio62.js → shield-off-8l0J6R-8.js} +1 -1
  92. package/ui/dist/assets/{skeleton-C9oEzNlU.js → skeleton-TFiFJ3nB.js} +1 -1
  93. package/ui/dist/assets/{smartphone-DusFfY0U.js → smartphone-77_g3SQn.js} +1 -1
  94. package/ui/dist/assets/{square-e1yxB2eR.js → square-Ckzsr3Rn.js} +1 -1
  95. package/ui/dist/assets/{switch-tNs0n-kz.js → switch-CAePkd6k.js} +1 -1
  96. package/ui/dist/assets/{table-CXxtLicg.js → table-ZjAZ7hkt.js} +1 -1
  97. package/ui/dist/assets/{tag-tAwMmQ9G.js → tag-C35mgoEv.js} +1 -1
  98. package/ui/dist/assets/{textarea-BjiEeIWh.js → textarea-DsVPnSgR.js} +1 -1
  99. package/ui/dist/assets/{timer-Vel1N3RP.js → timer-DHJBS2w1.js} +1 -1
  100. package/ui/dist/assets/{tooltip-ZMwtfr1J.js → tooltip-BYmQ331W.js} +1 -1
  101. package/ui/dist/assets/{trash-2-eM-CNwsZ.js → trash-2-keO5tKPE.js} +1 -1
  102. package/ui/dist/assets/{trending-up-BGafmjRo.js → trending-up-BBv8KbrY.js} +1 -1
  103. package/ui/dist/assets/{wand-sparkles-DPvcd9Pi.js → wand-sparkles-BusQ52ZK.js} +1 -1
  104. package/ui/dist/assets/{wrench-B8s3zYVV.js → wrench-02YaOSiK.js} +1 -1
  105. package/ui/dist/index.html +2 -2
  106. package/dist/chat/auto-reply.d.ts +0 -90
  107. package/dist/chat/auto-reply.js +0 -272
  108. package/dist/chat/execution/pty.d.ts +0 -92
  109. package/dist/chat/execution/pty.js +0 -285
  110. package/dist/chat/execution/tools/glinr-ops.d.ts +0 -202
  111. package/dist/chat/execution/tools/glinr-ops.js +0 -694
  112. package/dist/cli/commands/browser.d.ts +0 -3
  113. package/dist/cli/commands/browser.js +0 -138
  114. package/dist/cli/commands/canvas.d.ts +0 -3
  115. package/dist/cli/commands/canvas.js +0 -155
  116. package/dist/cli/commands/nodes.d.ts +0 -3
  117. package/dist/cli/commands/nodes.js +0 -193
  118. package/dist/diagnostics/exporters.d.ts +0 -21
  119. package/dist/diagnostics/exporters.js +0 -138
  120. package/dist/diagnostics/index.d.ts +0 -10
  121. package/dist/diagnostics/index.js +0 -9
  122. package/dist/diagnostics/middleware.d.ts +0 -12
  123. package/dist/diagnostics/middleware.js +0 -58
  124. package/dist/diagnostics/tracer.d.ts +0 -133
  125. package/dist/diagnostics/tracer.js +0 -257
  126. package/dist/discovery/index.d.ts +0 -8
  127. package/dist/discovery/index.js +0 -7
  128. package/dist/discovery/mdns.d.ts +0 -40
  129. package/dist/discovery/mdns.js +0 -249
  130. package/dist/plugins/registry.d.ts +0 -122
  131. package/dist/plugins/registry.js +0 -352
  132. package/dist/plugins/sandbox.d.ts +0 -77
  133. package/dist/plugins/sandbox.js +0 -287
  134. package/dist/plugins/search/brave.d.ts +0 -9
  135. package/dist/plugins/search/brave.js +0 -157
  136. package/dist/plugins/search/duckduckgo.d.ts +0 -9
  137. package/dist/plugins/search/duckduckgo.js +0 -171
  138. package/dist/plugins/search/index.d.ts +0 -13
  139. package/dist/plugins/search/index.js +0 -23
  140. package/dist/plugins/search/searxng.d.ts +0 -9
  141. package/dist/plugins/search/searxng.js +0 -170
  142. package/dist/plugins/search/serper.d.ts +0 -9
  143. package/dist/plugins/search/serper.js +0 -135
  144. package/dist/plugins/search/tavily.d.ts +0 -9
  145. package/dist/plugins/search/tavily.js +0 -146
  146. package/dist/providers/adapters/anthropic.d.ts +0 -26
  147. package/dist/providers/adapters/anthropic.js +0 -306
  148. package/dist/providers/adapters/base.d.ts +0 -53
  149. package/dist/providers/adapters/base.js +0 -99
  150. package/dist/providers/adapters/ollama.d.ts +0 -30
  151. package/dist/providers/adapters/ollama.js +0 -292
  152. package/dist/utils/ai-cache.d.ts +0 -72
  153. package/dist/utils/ai-cache.js +0 -204
  154. package/dist/utils/file-lock.d.ts +0 -85
  155. package/dist/utils/file-lock.js +0 -204
  156. package/ui/dist/assets/index-WclMPci1.css +0 -1
@@ -11,14 +11,26 @@
11
11
  import { Command } from 'commander';
12
12
  import chalk from 'chalk';
13
13
  import figlet from 'figlet';
14
- import { select, input, password as passwordPrompt, confirm } from '@inquirer/prompts';
14
+ import { select, search, input, password as passwordPrompt, confirm, Separator } from '@inquirer/prompts';
15
15
  import { randomUUID } from 'crypto';
16
16
  import { eq } from 'drizzle-orm';
17
- import { initStorage, getDb, saveProviderConfig } from '../../storage/index.js';
17
+ import { spawn } from 'child_process';
18
+ import { existsSync, mkdirSync, openSync } from 'node:fs';
19
+ import { join } from 'node:path';
20
+ import { createServer } from 'net';
21
+ import { initStorage, getDb, saveProviderConfig, loadAllProviderConfigs } from '../../storage/index.js';
18
22
  import { users, inviteCodes } from '../../storage/schema.js';
23
+ import { PROVIDER_CATALOG } from '../providers.js';
19
24
  import { hashPassword, validatePasswordStrength, generateRecoveryCodes, hashRecoveryCodes, generateInviteCode, hashInviteCode, } from '../../auth/password.js';
20
25
  import { updateSettings } from '../../settings/index.js';
21
26
  import { success, error, info, warn, spinner } from '../utils/output.js';
27
+ /** Render a step header with progress bar. */
28
+ function stepHeader(step, total, title) {
29
+ const barLen = 20;
30
+ const filled = Math.round((step / total) * barLen);
31
+ const bar = chalk.cyan('━'.repeat(filled)) + chalk.dim('━'.repeat(barLen - filled));
32
+ console.log(`\n ${bar} ${chalk.dim(`${step}/${total}`)} ${chalk.bold.white(title)}\n`);
33
+ }
22
34
  // Helpers
23
35
  function showBanner() {
24
36
  const banner = figlet.textSync('profClaw', {
@@ -122,32 +134,46 @@ function hasEnvKey(key) {
122
134
  return !!val && val !== '' && !val.startsWith('sk-ant-xxxx') && !val.startsWith('sk-xxxx');
123
135
  }
124
136
  // Step 1: System Check
137
+ async function detectConfiguredProvider() {
138
+ // Check env vars first
139
+ for (const p of PROVIDER_CATALOG) {
140
+ if (hasEnvKey(p.envVar))
141
+ return p.name;
142
+ }
143
+ // Check database (saved via saveProviderConfig)
144
+ try {
145
+ const saved = await loadAllProviderConfigs();
146
+ const enabled = saved.find(s => s.enabled);
147
+ if (enabled) {
148
+ const match = PROVIDER_CATALOG.find(p => p.key === enabled.type);
149
+ return match ? `${match.name} (saved)` : enabled.type;
150
+ }
151
+ }
152
+ catch { /* db not ready yet */ }
153
+ return null;
154
+ }
125
155
  async function stepSystemCheck(db) {
126
- console.log(chalk.bold.white('\n Step 1: System Check\n'));
127
156
  const spin = spinner('Checking system...');
128
157
  spin.start();
129
158
  const redisOk = await checkRedis();
130
159
  const hasAdmin = await checkAdminExists(db);
131
- const hasAnthropicKey = hasEnvKey('ANTHROPIC_API_KEY');
132
- const hasOpenAIKey = hasEnvKey('OPENAI_API_KEY');
160
+ const providerName = await detectConfiguredProvider();
161
+ const hasProvider = !!providerName;
133
162
  spin.stop();
134
- const mark = (ok) => (ok ? chalk.green(' ') : chalk.red(' '));
163
+ const mark = (ok) => (ok ? chalk.green(' \u2713') : chalk.red(' \u2717'));
135
164
  console.log(`${mark(redisOk)} Redis: ${redisOk ? 'connected' : 'not reachable'}`);
136
165
  console.log(`${mark(hasAdmin)} Admin account: ${hasAdmin ? 'exists' : 'not created'}`);
137
- console.log(`${mark(hasAnthropicKey || hasOpenAIKey)} AI provider: ${hasAnthropicKey ? 'Anthropic key found' :
138
- hasOpenAIKey ? 'OpenAI key found' :
139
- 'not configured'}`);
166
+ console.log(`${mark(hasProvider)} AI provider: ${providerName || 'not configured'}`);
140
167
  if (!redisOk) {
141
168
  warn('Redis is required for task processing.');
142
169
  info('Start Redis: docker compose up redis -d');
143
170
  }
144
- return { redisOk, hasAdmin, hasAnthropicKey, hasOpenAIKey };
171
+ return { redisOk, hasAdmin, hasProvider, providerName };
145
172
  }
146
173
  // Step 2: AI Provider
147
174
  async function stepAiProvider(status) {
148
- if (status.hasAnthropicKey || status.hasOpenAIKey) {
149
- const provider = status.hasAnthropicKey ? 'Anthropic' : 'OpenAI';
150
- info(`AI provider already configured (${provider} from env).`);
175
+ if (status.hasProvider) {
176
+ info(`AI provider already configured (${status.providerName}).`);
151
177
  try {
152
178
  const reconfigure = await confirm({
153
179
  message: 'Reconfigure AI provider?',
@@ -155,22 +181,63 @@ async function stepAiProvider(status) {
155
181
  theme,
156
182
  });
157
183
  if (!reconfigure)
158
- return 'skip';
184
+ return status.providerName || 'configured';
159
185
  }
160
186
  catch {
161
- return 'skip';
187
+ return status.providerName || 'configured';
162
188
  }
163
189
  }
190
+ // Detect which providers already have keys
191
+ const detected = new Set();
192
+ for (const p of PROVIDER_CATALOG) {
193
+ if (process.env[p.envVar])
194
+ detected.add(p.key);
195
+ }
196
+ // Check Ollama service
197
+ const ollamaRunning = await checkOllama(process.env.OLLAMA_BASE_URL || 'http://localhost:11434');
198
+ if (ollamaRunning)
199
+ detected.add('ollama');
164
200
  let choice;
165
201
  try {
166
- choice = await select({
167
- message: 'Select AI Provider',
168
- choices: [
169
- { name: `Anthropic ${chalk.green('(recommended)')}`, value: 'anthropic', description: 'Claude 4.x, 3.5 Sonnet' },
170
- { name: 'OpenAI', value: 'openai', description: 'GPT-4o, o1, o3' },
171
- { name: `Ollama ${chalk.cyan('(free, local)')}`, value: 'ollama', description: 'Llama, Mistral, Qwen - runs on your machine' },
172
- { name: chalk.dim('Skip for now'), value: 'skip', description: 'Configure later in Settings' },
173
- ],
202
+ choice = await search({
203
+ message: 'Select AI provider (type to filter, arrows to navigate)',
204
+ pageSize: 15,
205
+ source: (term) => {
206
+ const results = [];
207
+ const filter = (term || '').toLowerCase();
208
+ let lastCategory = '';
209
+ for (const p of PROVIDER_CATALOG) {
210
+ if (filter && ![p.name, p.key, p.models, p.category, p.tag]
211
+ .some(s => s.toLowerCase().includes(filter))) {
212
+ continue;
213
+ }
214
+ if (p.category !== lastCategory) {
215
+ if (results.length > 0)
216
+ results.push(new Separator());
217
+ results.push(new Separator(chalk.bold.cyan(` ${p.category} `)));
218
+ lastCategory = p.category;
219
+ }
220
+ const det = detected.has(p.key) ? chalk.green(' *') : '';
221
+ const ollamaTag = p.key === 'ollama' && ollamaRunning ? 'detected' : p.tag;
222
+ const tag = ollamaTag ? ` ${chalk.dim(`[${ollamaTag}]`)}` : '';
223
+ results.push({
224
+ name: `${p.name.padEnd(16)} ${chalk.dim(p.models)}${tag}${det}`,
225
+ value: p.key,
226
+ description: p.envVar,
227
+ short: p.name,
228
+ });
229
+ }
230
+ if (!filter || 'skip'.includes(filter)) {
231
+ results.push(new Separator());
232
+ results.push({
233
+ name: chalk.dim('Skip for now'),
234
+ value: 'skip',
235
+ description: 'Configure later in Settings',
236
+ short: 'Skip',
237
+ });
238
+ }
239
+ return results;
240
+ },
174
241
  theme,
175
242
  });
176
243
  }
@@ -181,58 +248,61 @@ async function stepAiProvider(status) {
181
248
  info('AI provider skipped. You can configure it later in Settings.');
182
249
  return 'skip';
183
250
  }
184
- if (choice === 'anthropic') {
185
- try {
186
- const apiKey = await input({ message: 'Anthropic API Key', theme });
187
- if (!apiKey) {
188
- warn('No key provided, skipping.');
189
- return 'skip';
190
- }
191
- await saveProviderConfig({ type: 'anthropic', apiKey, enabled: true });
192
- success('Anthropic provider configured.');
193
- return 'anthropic';
251
+ const chosen = PROVIDER_CATALOG.find(p => p.key === choice);
252
+ if (!chosen)
253
+ return 'skip';
254
+ console.log(`\n ${chalk.bold(chosen.name)} selected`);
255
+ console.log(chalk.dim(` Env var: ${chosen.envVar}`));
256
+ // Ollama special case
257
+ if (chosen.key === 'ollama') {
258
+ const ollamaUrl = process.env.OLLAMA_BASE_URL || 'http://localhost:11434';
259
+ const spin = spinner(`Checking Ollama at ${ollamaUrl}...`);
260
+ spin.start();
261
+ const ollamaOk = await checkOllama(ollamaUrl);
262
+ spin.stop();
263
+ if (ollamaOk) {
264
+ success(`Ollama reachable at ${ollamaUrl}`);
265
+ await saveProviderConfig({ type: 'ollama', apiKey: ollamaUrl, enabled: true });
266
+ await updateSettings({
267
+ integrations: { ollamaEndpoint: ollamaUrl },
268
+ plugins: { ollama: { enabled: true, autoStart: false } },
269
+ });
270
+ success('Ollama provider configured.');
194
271
  }
195
- catch {
196
- return 'skip';
272
+ else {
273
+ warn(`Ollama not reachable at ${ollamaUrl}`);
274
+ info('Start Ollama: docker compose --profile ai up -d');
275
+ info('Then re-run: profclaw setup');
276
+ await saveProviderConfig({ type: 'ollama', apiKey: ollamaUrl, enabled: true });
197
277
  }
278
+ return 'ollama';
198
279
  }
199
- if (choice === 'openai') {
200
- try {
201
- const apiKey = await input({ message: 'OpenAI API Key', theme });
202
- if (!apiKey) {
203
- warn('No key provided, skipping.');
204
- return 'skip';
205
- }
206
- await saveProviderConfig({ type: 'openai', apiKey, enabled: true });
207
- success('OpenAI provider configured.');
208
- return 'openai';
209
- }
210
- catch {
211
- return 'skip';
212
- }
280
+ // Already detected
281
+ if (detected.has(chosen.key)) {
282
+ success(`${chosen.name} already configured from ${chosen.envVar}`);
283
+ return chosen.key;
213
284
  }
214
- // Ollama
215
- const ollamaUrl = process.env.OLLAMA_BASE_URL || 'http://localhost:11434';
216
- const spin = spinner(`Checking Ollama at ${ollamaUrl}...`);
217
- spin.start();
218
- const ollamaOk = await checkOllama(ollamaUrl);
219
- spin.stop();
220
- if (ollamaOk) {
221
- success(`Ollama reachable at ${ollamaUrl}`);
222
- await saveProviderConfig({ type: 'ollama', apiKey: ollamaUrl, enabled: true });
223
- await updateSettings({
224
- integrations: { ollamaEndpoint: ollamaUrl },
225
- plugins: { ollama: { enabled: true, autoStart: false } },
226
- });
227
- success('Ollama provider configured.');
285
+ // Prompt for API key
286
+ let apiKey;
287
+ try {
288
+ apiKey = await input({ message: chosen.envVar, theme });
228
289
  }
229
- else {
230
- warn(`Ollama not reachable at ${ollamaUrl}`);
231
- info('Start Ollama: docker compose --profile ai up -d');
232
- info('Then re-run: profclaw setup');
233
- await saveProviderConfig({ type: 'ollama', apiKey: ollamaUrl, enabled: true });
290
+ catch {
291
+ return 'skip';
292
+ }
293
+ if (!apiKey) {
294
+ warn('No key provided, skipping.');
295
+ return 'skip';
296
+ }
297
+ try {
298
+ await saveProviderConfig({ type: chosen.key, apiKey, enabled: true });
299
+ success(`${chosen.name} provider configured.`);
300
+ return chosen.key;
301
+ }
302
+ catch (err) {
303
+ error(`Failed to save: ${err instanceof Error ? err.message : 'unknown'}`);
304
+ return 'skip';
234
305
  }
235
- return 'ollama';
236
306
  }
237
307
  // Step 3: Admin Account
238
308
  async function createAdminUser(db, name, email, password) {
@@ -267,7 +337,6 @@ async function stepAdminAccount(db) {
267
337
  error('Database not available. Cannot create admin account.');
268
338
  return null;
269
339
  }
270
- console.log(chalk.bold.white('\n Step 3: Admin Account\n'));
271
340
  const existingAdmins = await getExistingAdmins(db);
272
341
  if (existingAdmins.length > 0) {
273
342
  console.log(chalk.dim(' Existing admin accounts:'));
@@ -414,7 +483,6 @@ async function stepAdminAccount(db) {
414
483
  }
415
484
  // Step 4: Registration Mode
416
485
  async function stepRegistrationMode(db) {
417
- console.log(chalk.bold.white('\n Step 4: Registration Mode\n'));
418
486
  let mode;
419
487
  try {
420
488
  mode = await select({
@@ -472,7 +540,6 @@ async function stepRegistrationMode(db) {
472
540
  }
473
541
  // Step 5: GitHub OAuth (optional)
474
542
  async function stepGitHubOAuth() {
475
- console.log(chalk.bold.white('\n Step 5: GitHub OAuth (Optional)\n'));
476
543
  let configure;
477
544
  try {
478
545
  configure = await confirm({
@@ -519,19 +586,111 @@ async function stepGitHubOAuth() {
519
586
  return true;
520
587
  }
521
588
  // Step 6: Summary
522
- function showSummary(results) {
523
- const port = process.env.PORT || '3000';
589
+ function checkPortAvailable(port) {
590
+ return new Promise((resolve) => {
591
+ const server = createServer();
592
+ server.once('error', () => resolve(false));
593
+ server.once('listening', () => { server.close(); resolve(true); });
594
+ server.listen(port);
595
+ });
596
+ }
597
+ async function showSummaryAndOffer(results) {
598
+ const port = parseInt(process.env.PORT || '3000', 10);
524
599
  console.log(chalk.bold.white('\n Setup Complete!\n'));
525
- console.log(' ' + ''.repeat(44));
526
- const mark = (ok) => (ok ? chalk.green('') : chalk.yellow(''));
600
+ console.log(' ' + '\u2500'.repeat(44));
601
+ const mark = (ok) => (ok ? chalk.green('\u2713') : chalk.yellow('\u2717'));
527
602
  console.log(` ${mark(results.redisOk)} Redis: ${results.redisOk ? 'connected' : 'not connected'}`);
528
603
  console.log(` ${mark(results.aiProvider !== 'skip')} AI: ${results.aiProvider === 'skip' ? 'not configured' : results.aiProvider}`);
529
604
  console.log(` ${mark(!!results.adminEmail)} Admin: ${results.adminEmail || 'not created'}`);
530
605
  console.log(` ${mark(true)} Registration: ${results.registrationMode}${results.inviteCodes.length ? ` (${results.inviteCodes.length} codes)` : ''}`);
531
606
  console.log(` ${mark(results.githubOAuth)} GitHub OAuth: ${results.githubOAuth ? 'configured' : 'skipped'}`);
532
- console.log(' ' + ''.repeat(44));
607
+ console.log(' ' + '\u2500'.repeat(44));
608
+ // Check if server is already running
609
+ const portFree = await checkPortAvailable(port);
610
+ const serverRunning = !portFree;
611
+ if (serverRunning) {
612
+ // Server already running - just show the URL
613
+ console.log('');
614
+ success(`Server already running on port ${port}`);
615
+ console.log(`\n ${chalk.cyan.bold(`http://localhost:${port}`)}\n`);
616
+ }
617
+ else {
618
+ // Offer to start the server
619
+ console.log('');
620
+ let startServer = false;
621
+ try {
622
+ startServer = await confirm({
623
+ message: 'Start the server now?',
624
+ default: true,
625
+ theme,
626
+ });
627
+ }
628
+ catch { /* Ctrl+C */ }
629
+ if (startServer) {
630
+ console.log('');
631
+ const startSpin = spinner('Starting server...');
632
+ startSpin.start();
633
+ // Determine runtime
634
+ const distPath = 'dist/server.js';
635
+ const useCompiled = existsSync(distPath);
636
+ const cmd = useCompiled ? 'node' : 'npx';
637
+ const args = useCompiled ? [distPath] : ['tsx', 'src/server.ts'];
638
+ // Redirect server output to log file (keep terminal clean)
639
+ const logDir = join(process.cwd(), 'data');
640
+ if (!existsSync(logDir))
641
+ mkdirSync(logDir, { recursive: true });
642
+ const logPath = join(logDir, 'server.log');
643
+ const logFd = openSync(logPath, 'a');
644
+ const child = spawn(cmd, args, {
645
+ stdio: ['ignore', logFd, logFd],
646
+ detached: true,
647
+ env: { ...process.env, PORT: String(port) },
648
+ });
649
+ child.unref();
650
+ // Wait for server to start, polling health check
651
+ let serverOk = false;
652
+ for (let i = 0; i < 5; i++) {
653
+ await new Promise(r => setTimeout(r, 1000));
654
+ try {
655
+ const res = await fetch(`http://localhost:${port}/health`, {
656
+ signal: AbortSignal.timeout(2000),
657
+ });
658
+ if (res.ok) {
659
+ serverOk = true;
660
+ break;
661
+ }
662
+ }
663
+ catch { /* not ready yet */ }
664
+ }
665
+ startSpin.stop();
666
+ if (serverOk) {
667
+ success(`Server running on port ${port}`);
668
+ }
669
+ else {
670
+ warn('Server starting in background (health check pending)');
671
+ }
672
+ console.log(`\n ${chalk.cyan.bold(`http://localhost:${port}`)}`);
673
+ console.log('');
674
+ console.log(chalk.dim(' Server is running in the background.'));
675
+ console.log(chalk.dim(` Stop: ${chalk.white('profclaw daemon stop')} or ${chalk.white(`kill $(lsof -ti :${port})`)}`));
676
+ console.log(chalk.dim(` Logs: ${chalk.white(`tail -f ${logPath}`)}`));
677
+ console.log(chalk.dim(` Status: ${chalk.white('profclaw doctor')}`));
678
+ }
679
+ else {
680
+ console.log('');
681
+ console.log(` Start later with: ${chalk.cyan.bold('profclaw serve')}`);
682
+ }
683
+ }
684
+ // Always show helpful next steps
533
685
  console.log('');
534
- console.log(` Open ${chalk.cyan.bold(`http://localhost:${port}`)} to get started.`);
686
+ console.log(chalk.bold.white(' Next Steps'));
687
+ console.log(' ' + '\u2500'.repeat(44));
688
+ console.log(` ${chalk.cyan('profclaw doctor')} Health check (12 tests)`);
689
+ console.log(` ${chalk.cyan('profclaw chat quick')} ${chalk.dim('"hi"')} Chat with AI`);
690
+ console.log(` ${chalk.cyan('profclaw task list')} View agent tasks`);
691
+ console.log(` ${chalk.cyan('profclaw tools list')} Available tools`);
692
+ console.log(` ${chalk.cyan('profclaw provider test')} Test AI provider`);
693
+ console.log(` ${chalk.cyan('profclaw --help')} All commands`);
535
694
  console.log('');
536
695
  }
537
696
  // Non-Interactive Mode
@@ -657,6 +816,7 @@ export function setupCommand() {
657
816
  .option('--admin-name <name>', 'Admin display name (non-interactive)')
658
817
  .option('--ai-provider <provider>', 'AI provider: anthropic, openai, ollama, skip (non-interactive)')
659
818
  .option('--registration-mode <mode>', 'Registration mode: invite, open (non-interactive)')
819
+ .option('--from-onboard', 'Called from onboard wizard (skip AI provider)')
660
820
  .action(async (options) => {
661
821
  try {
662
822
  // Non-interactive mode
@@ -670,36 +830,79 @@ export function setupCommand() {
670
830
  });
671
831
  return;
672
832
  }
833
+ const fromOnboard = !!options.fromOnboard;
673
834
  // Interactive mode
674
- showBanner();
835
+ if (!fromOnboard)
836
+ showBanner();
837
+ // Suppress initialization noise ([INFO] Initializing LibSQL storage, etc.)
838
+ const _origLog = console.log;
839
+ console.log = (...args) => {
840
+ const msg = String(args[0] || '');
841
+ if (msg.includes('[INFO]') || msg.includes('[DEBUG]'))
842
+ return;
843
+ _origLog.apply(console, args);
844
+ };
675
845
  const spin = spinner('Initializing...');
676
846
  spin.start();
677
847
  await initStorage();
678
848
  const db = getDb();
679
849
  spin.stop();
850
+ // Restore console
851
+ console.log = _origLog;
680
852
  if (!db) {
681
853
  error('Failed to initialize database.');
682
854
  process.exit(1);
683
855
  }
684
- // Step 1: System Check
685
- const status = await stepSystemCheck(db);
686
- // Step 2: AI Provider
687
- console.log(chalk.bold.white('\n Step 2: AI Provider\n'));
688
- const aiProvider = await stepAiProvider(status);
689
- // Step 3: Admin Account
856
+ // Dynamic step numbering
857
+ // Standalone: 5 steps (System Check, AI Provider, Admin, Registration, GitHub OAuth)
858
+ // From onboard: continues at step 5 of 6 (onboard did 1-4)
859
+ const totalSteps = fromOnboard ? 6 : 5;
860
+ let step = fromOnboard ? 4 : 0;
861
+ // System Check (skip if from onboard - already done)
862
+ let status;
863
+ if (fromOnboard) {
864
+ // Quick silent check
865
+ const redisOk = await checkRedis();
866
+ const hasAdmin = await checkAdminExists(db);
867
+ const providerName = await detectConfiguredProvider();
868
+ status = { redisOk, hasAdmin, hasProvider: !!providerName, providerName };
869
+ }
870
+ else {
871
+ step++;
872
+ stepHeader(step, totalSteps, 'System Check');
873
+ status = await stepSystemCheck(db);
874
+ }
875
+ // AI Provider (skip if from onboard - already configured)
876
+ let aiProvider = 'skip';
877
+ if (!fromOnboard) {
878
+ step++;
879
+ stepHeader(step, totalSteps, 'AI Provider');
880
+ aiProvider = await stepAiProvider(status);
881
+ }
882
+ // Admin Account
883
+ step++;
884
+ stepHeader(step, totalSteps, 'Admin Account');
690
885
  const adminEmail = await stepAdminAccount(db);
691
- // Step 4: Registration Mode
886
+ // Registration Mode
887
+ step++;
888
+ stepHeader(step, totalSteps, 'Registration Mode');
692
889
  const regResult = await stepRegistrationMode(db);
693
- // Step 5: GitHub OAuth
694
- const githubOAuth = await stepGitHubOAuth();
695
- // Step 6: Summary
696
- showSummary({
890
+ // GitHub OAuth (skip if from onboard to keep it shorter)
891
+ let githubOAuth = false;
892
+ if (!fromOnboard) {
893
+ step++;
894
+ stepHeader(step, totalSteps, 'GitHub OAuth');
895
+ githubOAuth = await stepGitHubOAuth();
896
+ }
897
+ // Summary + offer to start server
898
+ await showSummaryAndOffer({
697
899
  redisOk: status.redisOk,
698
900
  aiProvider,
699
901
  adminEmail,
700
902
  registrationMode: regResult.mode,
701
903
  inviteCodes: regResult.codes,
702
904
  githubOAuth,
905
+ fromOnboard,
703
906
  });
704
907
  }
705
908
  catch (err) {
package/dist/cli/index.js CHANGED
@@ -23,17 +23,14 @@ import { sessionCommands } from './commands/session.js';
23
23
  import { doctorCommand } from './commands/doctor.js';
24
24
  import { providerCommands } from './commands/provider.js';
25
25
  import { mcpCommands } from './commands/mcp.js';
26
- import { browserCommands } from './commands/browser.js';
27
26
  import { devicesCommands } from './commands/devices.js';
28
27
  import { daemonCommand } from './commands/daemon.js';
29
28
  import { completionCommands } from './commands/completion.js';
30
- import { canvasCommands } from './commands/canvas.js';
31
29
  import { webhooksCommands } from './commands/webhooks.js';
32
30
  import { channelsCommands } from './commands/channels.js';
33
31
  import { securityCommands } from './commands/security.js';
34
32
  import { logsCommand } from './commands/logs.js';
35
33
  import { modelsCommands } from './commands/models.js';
36
- import { nodesCommands } from './commands/nodes.js';
37
34
  import { tuiCommand } from './commands/tui.js';
38
35
  const VERSION = '2.0.0';
39
36
  // ASCII Banner
@@ -77,17 +74,14 @@ program.addCommand(doctorCommand());
77
74
  program.addCommand(providerCommands());
78
75
  program.addCommand(mcpCommands());
79
76
  // Phase 9 - OpenClaw parity commands
80
- program.addCommand(browserCommands());
81
77
  program.addCommand(devicesCommands());
82
78
  program.addCommand(daemonCommand());
83
79
  program.addCommand(completionCommands());
84
- program.addCommand(canvasCommands());
85
80
  program.addCommand(webhooksCommands());
86
81
  program.addCommand(channelsCommands());
87
82
  program.addCommand(securityCommands());
88
83
  program.addCommand(logsCommand());
89
84
  program.addCommand(modelsCommands());
90
- program.addCommand(nodesCommands());
91
85
  program.addCommand(tuiCommand());
92
86
  // Default action (no command) - show banner and help
93
87
  program.action(() => {
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Shared AI provider catalog for CLI wizards (onboard + setup).
3
+ */
4
+ export interface ProviderEntry {
5
+ key: string;
6
+ name: string;
7
+ models: string;
8
+ envVar: string;
9
+ category: string;
10
+ tag: string;
11
+ }
12
+ export declare const PROVIDER_CATALOG: ProviderEntry[];
13
+ /** All env var keys used by providers (for .env writing). */
14
+ export declare const PROVIDER_ENV_KEYS: string[];
15
+ //# sourceMappingURL=providers.d.ts.map
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Shared AI provider catalog for CLI wizards (onboard + setup).
3
+ */
4
+ export const PROVIDER_CATALOG = [
5
+ // Popular
6
+ { key: 'anthropic', name: 'Anthropic', models: 'Claude 4.x, 3.5 Sonnet', envVar: 'ANTHROPIC_API_KEY', category: 'Popular', tag: 'recommended' },
7
+ { key: 'openai', name: 'OpenAI', models: 'GPT-4o, o1, o3', envVar: 'OPENAI_API_KEY', category: 'Popular', tag: '' },
8
+ { key: 'google', name: 'Google AI', models: 'Gemini 2.x, Flash', envVar: 'GOOGLE_GENERATIVE_AI_API_KEY', category: 'Popular', tag: '' },
9
+ { key: 'ollama', name: 'Ollama', models: 'Llama, Mistral, Qwen', envVar: 'OLLAMA_BASE_URL', category: 'Popular', tag: 'free, local' },
10
+ // Fast Inference
11
+ { key: 'groq', name: 'Groq', models: 'Llama, Mixtral', envVar: 'GROQ_API_KEY', category: 'Fast Inference', tag: 'fast' },
12
+ { key: 'cerebras', name: 'Cerebras', models: 'Llama 3.3 70B', envVar: 'CEREBRAS_API_KEY', category: 'Fast Inference', tag: 'fast' },
13
+ { key: 'fireworks', name: 'Fireworks', models: 'Llama, Mixtral', envVar: 'FIREWORKS_API_KEY', category: 'Fast Inference', tag: '' },
14
+ { key: 'together', name: 'Together', models: 'Open models', envVar: 'TOGETHER_API_KEY', category: 'Fast Inference', tag: '' },
15
+ // Enterprise
16
+ { key: 'azure', name: 'Azure OpenAI', models: 'GPT-4o', envVar: 'AZURE_OPENAI_API_KEY', category: 'Enterprise', tag: 'enterprise' },
17
+ { key: 'bedrock', name: 'AWS Bedrock', models: 'Claude, Titan, Llama', envVar: 'AWS_ACCESS_KEY_ID', category: 'Enterprise', tag: 'enterprise' },
18
+ { key: 'watsonx', name: 'IBM Watsonx', models: 'Granite, Llama', envVar: 'WATSONX_API_KEY', category: 'Enterprise', tag: 'enterprise' },
19
+ // More Providers
20
+ { key: 'deepseek', name: 'DeepSeek', models: 'V3, R1 (reasoning)', envVar: 'DEEPSEEK_API_KEY', category: 'More Providers', tag: '' },
21
+ { key: 'mistral', name: 'Mistral', models: 'Large, Codestral', envVar: 'MISTRAL_API_KEY', category: 'More Providers', tag: '' },
22
+ { key: 'xai', name: 'xAI', models: 'Grok', envVar: 'XAI_API_KEY', category: 'More Providers', tag: '' },
23
+ { key: 'perplexity', name: 'Perplexity', models: 'Sonar (search)', envVar: 'PERPLEXITY_API_KEY', category: 'More Providers', tag: 'search' },
24
+ { key: 'cohere', name: 'Cohere', models: 'Command R+', envVar: 'COHERE_API_KEY', category: 'More Providers', tag: '' },
25
+ // Aggregators
26
+ { key: 'openrouter', name: 'OpenRouter', models: 'Any model (proxy)', envVar: 'OPENROUTER_API_KEY', category: 'Aggregators', tag: 'multi-model' },
27
+ { key: 'replicate', name: 'Replicate', models: 'Open-source models', envVar: 'REPLICATE_API_TOKEN', category: 'Aggregators', tag: '' },
28
+ { key: 'github-models', name: 'GitHub Models', models: 'Azure-backed', envVar: 'GITHUB_TOKEN', category: 'Aggregators', tag: '' },
29
+ { key: 'huggingface', name: 'HuggingFace', models: 'Inference API', envVar: 'HF_TOKEN', category: 'Aggregators', tag: '' },
30
+ { key: 'nvidia-nim', name: 'NVIDIA NIM', models: 'Llama, Nemotron', envVar: 'NVIDIA_NIM_API_KEY', category: 'Aggregators', tag: '' },
31
+ // Asia
32
+ { key: 'zhipu', name: 'Zhipu AI', models: 'GLM-4, GLM-5', envVar: 'ZHIPU_API_KEY', category: 'Asia', tag: '' },
33
+ { key: 'moonshot', name: 'Moonshot', models: 'Kimi (128K context)', envVar: 'MOONSHOT_API_KEY', category: 'Asia', tag: '' },
34
+ { key: 'qwen', name: 'Qwen', models: 'Qwen Max, Plus', envVar: 'QWEN_API_KEY', category: 'Asia', tag: '' },
35
+ { key: 'minimax', name: 'MiniMax', models: 'abab series', envVar: 'MINIMAX_API_KEY', category: 'Asia', tag: '' },
36
+ { key: 'volcengine', name: 'Volcengine', models: 'Doubao (Bytedance)', envVar: 'VOLCENGINE_API_KEY', category: 'Asia', tag: '' },
37
+ { key: 'qianfan', name: 'Qianfan', models: 'ERNIE (Baidu)', envVar: 'QIANFAN_API_KEY', category: 'Asia', tag: '' },
38
+ ];
39
+ /** All env var keys used by providers (for .env writing). */
40
+ export const PROVIDER_ENV_KEYS = PROVIDER_CATALOG.map(p => p.envVar);
41
+ //# sourceMappingURL=providers.js.map
@@ -9,6 +9,8 @@ const MODE_CAPABILITIES = {
9
9
  'agent',
10
10
  'tools',
11
11
  'chat_cli',
12
+ 'chat_channels',
13
+ 'cron',
12
14
  ]),
13
15
  mini: new Set([
14
16
  'agent',