@commandable/mcp 0.0.7 → 0.1.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 (217) hide show
  1. package/README.md +28 -31
  2. package/dist/app/nitro.json +15 -0
  3. package/dist/app/public/_fonts/57NSSoFy1VLVs2gqly8Ls9awBnZMFyXGrefpmqvdqmc-zJfbBtpgM4cDmcXBsqZNW79_kFnlpPd62b48glgdydA.woff2 +0 -0
  4. package/dist/app/public/_fonts/8VR2wSMN-3U4NbWAVYXlkRV6hA0jFBXP-0RtL3X7fko-x2gYI4qfmkRdxyQQUPaBZdZdgl1TeVrquF_TxHeM4lM.woff2 +0 -0
  5. package/dist/app/public/_fonts/GsKUclqeNLJ96g5AU593ug6yanivOiwjW_7zESNPChw-jHA4tBeM1bjF7LATGUpfBuSTyomIFrWBTzjF7txVYfg.woff2 +0 -0
  6. package/dist/app/public/_fonts/Ld1FnTo3yTIwDyGfTQ5-Fws9AWsCbKfMvgxduXr7JcY-W25bL8NF1fjpLRSOgJb7RoZPHqGQNwMTM7S9tHVoxx8.woff2 +0 -0
  7. package/dist/app/public/_fonts/NdzqRASp2bovDUhQT1IRE_EMqKJ2KYQdTCfFcBvL8yw-KhwZiS86o3fErOe5GGMExHUemmI_dBfaEFxjISZrBd0.woff2 +0 -0
  8. package/dist/app/public/_fonts/iTkrULNFJJkTvihIg1Vqi5IODRH_9btXCioVF5l98I8-AndUyau2HR2felA_ra8V2mutQgschhasE5FD1dXGJX8.woff2 +0 -0
  9. package/dist/app/public/_nuxt/-tOYwuj2.js +30 -0
  10. package/dist/app/public/_nuxt/BdctKXor.js +1 -0
  11. package/dist/app/public/_nuxt/BlP7Uu-5.js +1 -0
  12. package/dist/app/public/_nuxt/CsbkV5Bd.js +1 -0
  13. package/dist/app/public/_nuxt/D-43HurL.js +59 -0
  14. package/dist/app/public/_nuxt/DU1mG77A.js +1 -0
  15. package/dist/app/public/_nuxt/_id_.BKAjWkoP.css +1 -0
  16. package/dist/app/public/_nuxt/builds/latest.json +1 -0
  17. package/dist/app/public/_nuxt/builds/meta/ceae497c-21d6-4278-8cc9-3c2c80f03dca.json +1 -0
  18. package/dist/app/public/_nuxt/entry.Y3mA4bzA.css +1 -0
  19. package/dist/app/public/_nuxt/error-404.C7fg894-.css +1 -0
  20. package/dist/app/public/_nuxt/error-500.DjUK_N2Y.css +1 -0
  21. package/dist/app/public/_nuxt/uS7FY2am.js +1 -0
  22. package/dist/app/public/favicon.ico +0 -0
  23. package/dist/app/server/chunks/_/error-500.mjs +19 -0
  24. package/dist/app/server/chunks/_/error-500.mjs.map +1 -0
  25. package/dist/app/server/chunks/_/icons.mjs +5933 -0
  26. package/dist/app/server/chunks/_/icons.mjs.map +1 -0
  27. package/dist/app/server/chunks/_/icons2.mjs +11338 -0
  28. package/dist/app/server/chunks/_/icons2.mjs.map +1 -0
  29. package/dist/app/server/chunks/build/IntegrationCredentials-styles.CULcCK6_.mjs +8 -0
  30. package/dist/app/server/chunks/build/IntegrationCredentials-styles.CULcCK6_.mjs.map +1 -0
  31. package/dist/app/server/chunks/build/_id_-DBwSV4AY.mjs +1354 -0
  32. package/dist/app/server/chunks/build/_id_-DBwSV4AY.mjs.map +1 -0
  33. package/dist/app/server/chunks/build/client.precomputed.mjs +4 -0
  34. package/dist/app/server/chunks/build/client.precomputed.mjs.map +1 -0
  35. package/dist/app/server/chunks/build/error-404-D2QibUBT.mjs +121 -0
  36. package/dist/app/server/chunks/build/error-404-D2QibUBT.mjs.map +1 -0
  37. package/dist/app/server/chunks/build/error-404-styles.Bvxdxqjk.mjs +8 -0
  38. package/dist/app/server/chunks/build/error-404-styles.Bvxdxqjk.mjs.map +1 -0
  39. package/dist/app/server/chunks/build/error-500-DYvawybF.mjs +104 -0
  40. package/dist/app/server/chunks/build/error-500-DYvawybF.mjs.map +1 -0
  41. package/dist/app/server/chunks/build/error-500-styles.BnYAAXSg.mjs +8 -0
  42. package/dist/app/server/chunks/build/error-500-styles.BnYAAXSg.mjs.map +1 -0
  43. package/dist/app/server/chunks/build/fetch-ZbqIFhDG.mjs +2851 -0
  44. package/dist/app/server/chunks/build/fetch-ZbqIFhDG.mjs.map +1 -0
  45. package/dist/app/server/chunks/build/index-5H-nmhph.mjs +68 -0
  46. package/dist/app/server/chunks/build/index-5H-nmhph.mjs.map +1 -0
  47. package/dist/app/server/chunks/build/index-C8flTcKI.mjs +720 -0
  48. package/dist/app/server/chunks/build/index-C8flTcKI.mjs.map +1 -0
  49. package/dist/app/server/chunks/build/server.mjs +8736 -0
  50. package/dist/app/server/chunks/build/server.mjs.map +1 -0
  51. package/dist/app/server/chunks/build/styles.mjs +12 -0
  52. package/dist/app/server/chunks/build/styles.mjs.map +1 -0
  53. package/dist/app/server/chunks/nitro/nitro.mjs +9359 -0
  54. package/dist/app/server/chunks/nitro/nitro.mjs.map +1 -0
  55. package/dist/app/server/chunks/routes/api/_commandable/status.get.mjs +59 -0
  56. package/dist/app/server/chunks/routes/api/_commandable/status.get.mjs.map +1 -0
  57. package/dist/app/server/chunks/routes/api/catalog/_type/tools.get.mjs +40 -0
  58. package/dist/app/server/chunks/routes/api/catalog/_type/tools.get.mjs.map +1 -0
  59. package/dist/app/server/chunks/routes/api/catalog/_type/toolsets.get.mjs +41 -0
  60. package/dist/app/server/chunks/routes/api/catalog/_type/toolsets.get.mjs.map +1 -0
  61. package/dist/app/server/chunks/routes/api/catalog.get.mjs +46 -0
  62. package/dist/app/server/chunks/routes/api/catalog.get.mjs.map +1 -0
  63. package/dist/app/server/chunks/routes/api/index.get.mjs +39 -0
  64. package/dist/app/server/chunks/routes/api/index.get.mjs.map +1 -0
  65. package/dist/app/server/chunks/routes/api/index.post.mjs +60 -0
  66. package/dist/app/server/chunks/routes/api/index.post.mjs.map +1 -0
  67. package/dist/app/server/chunks/routes/api/integrations/_id/credentials-config.get.mjs +63 -0
  68. package/dist/app/server/chunks/routes/api/integrations/_id/credentials-config.get.mjs.map +1 -0
  69. package/dist/app/server/chunks/routes/api/integrations/_id/credentials-status.get.mjs +70 -0
  70. package/dist/app/server/chunks/routes/api/integrations/_id/credentials-status.get.mjs.map +1 -0
  71. package/dist/app/server/chunks/routes/api/integrations/_id/credentials.delete.mjs +61 -0
  72. package/dist/app/server/chunks/routes/api/integrations/_id/credentials.delete.mjs.map +1 -0
  73. package/dist/app/server/chunks/routes/api/integrations/_id/credentials.post.mjs +94 -0
  74. package/dist/app/server/chunks/routes/api/integrations/_id/credentials.post.mjs.map +1 -0
  75. package/dist/app/server/chunks/routes/api/integrations/_id/permissions.post.mjs +54 -0
  76. package/dist/app/server/chunks/routes/api/integrations/_id/permissions.post.mjs.map +1 -0
  77. package/dist/app/server/chunks/routes/api/integrations/_id/toolsets.post.mjs +53 -0
  78. package/dist/app/server/chunks/routes/api/integrations/_id/toolsets.post.mjs.map +1 -0
  79. package/dist/app/server/chunks/routes/api/integrations/_id_.delete.mjs +44 -0
  80. package/dist/app/server/chunks/routes/api/integrations/_id_.delete.mjs.map +1 -0
  81. package/dist/app/server/chunks/routes/health.get.mjs +37 -0
  82. package/dist/app/server/chunks/routes/health.get.mjs.map +1 -0
  83. package/dist/app/server/chunks/routes/mcp/create.mjs +56 -0
  84. package/dist/app/server/chunks/routes/mcp/create.mjs.map +1 -0
  85. package/dist/app/server/chunks/routes/mcp.mjs +56 -0
  86. package/dist/app/server/chunks/routes/mcp.mjs.map +1 -0
  87. package/dist/app/server/chunks/routes/renderer.mjs +492 -0
  88. package/dist/app/server/chunks/routes/renderer.mjs.map +1 -0
  89. package/dist/app/server/chunks/virtual/_virtual_spa-template.mjs +4 -0
  90. package/dist/app/server/chunks/virtual/_virtual_spa-template.mjs.map +1 -0
  91. package/dist/app/server/index.mjs +31 -0
  92. package/dist/app/server/index.mjs.map +1 -0
  93. package/dist/app/server/migrations/pg/0000_initial.sql +74 -0
  94. package/dist/app/server/migrations/pg/meta/_journal.json +13 -0
  95. package/dist/app/server/migrations/sqlite/0000_initial.sql +74 -0
  96. package/dist/app/server/migrations/sqlite/meta/_journal.json +13 -0
  97. package/dist/app/server/package.json +108 -0
  98. package/dist/cli/credentialManager.d.ts.map +1 -1
  99. package/dist/cli/credentialManager.js +5 -4
  100. package/dist/cli/credentialManager.js.map +1 -1
  101. package/dist/cli/index.d.ts +15 -0
  102. package/dist/cli/index.d.ts.map +1 -1
  103. package/dist/cli/index.js +656 -17
  104. package/dist/cli/index.js.map +1 -1
  105. package/dist/cli/setup.d.ts.map +1 -1
  106. package/dist/cli/setup.js +124 -33
  107. package/dist/cli/setup.js.map +1 -1
  108. package/dist/config/configApply.js +1 -1
  109. package/dist/config/configApply.js.map +1 -1
  110. package/dist/config/configSchema.d.ts +6 -2
  111. package/dist/config/configSchema.d.ts.map +1 -1
  112. package/dist/config/configSchema.js +2 -1
  113. package/dist/config/configSchema.js.map +1 -1
  114. package/dist/db/client.d.ts +3 -1
  115. package/dist/db/client.d.ts.map +1 -1
  116. package/dist/db/client.js.map +1 -1
  117. package/dist/db/credentialStore.d.ts +2 -0
  118. package/dist/db/credentialStore.d.ts.map +1 -1
  119. package/dist/db/credentialStore.js +15 -16
  120. package/dist/db/credentialStore.js.map +1 -1
  121. package/dist/db/integrationStore.d.ts +7 -0
  122. package/dist/db/integrationStore.d.ts.map +1 -1
  123. package/dist/db/integrationStore.js +61 -31
  124. package/dist/db/integrationStore.js.map +1 -1
  125. package/dist/db/integrationTypeConfigStore.d.ts +6 -0
  126. package/dist/db/integrationTypeConfigStore.d.ts.map +1 -0
  127. package/dist/db/integrationTypeConfigStore.js +94 -0
  128. package/dist/db/integrationTypeConfigStore.js.map +1 -0
  129. package/dist/db/migrate.d.ts.map +1 -1
  130. package/dist/db/migrate.js +8 -109
  131. package/dist/db/migrate.js.map +1 -1
  132. package/dist/db/migrations/pg/0000_initial.sql +74 -0
  133. package/dist/db/migrations/pg/meta/_journal.json +13 -0
  134. package/dist/db/migrations/sqlite/0000_initial.sql +74 -0
  135. package/dist/db/migrations/sqlite/meta/_journal.json +13 -0
  136. package/dist/db/schema.d.ts +961 -153
  137. package/dist/db/schema.d.ts.map +1 -1
  138. package/dist/db/schema.js +55 -3
  139. package/dist/db/schema.js.map +1 -1
  140. package/dist/db/toolDefinitionStore.d.ts +6 -0
  141. package/dist/db/toolDefinitionStore.d.ts.map +1 -0
  142. package/dist/db/toolDefinitionStore.js +95 -0
  143. package/dist/db/toolDefinitionStore.js.map +1 -0
  144. package/dist/index.d.ts +7 -0
  145. package/dist/index.d.ts.map +1 -1
  146. package/dist/index.js +7 -0
  147. package/dist/index.js.map +1 -1
  148. package/dist/integrations/actionsFactory.d.ts +5 -1
  149. package/dist/integrations/actionsFactory.d.ts.map +1 -1
  150. package/dist/integrations/actionsFactory.js +37 -14
  151. package/dist/integrations/actionsFactory.js.map +1 -1
  152. package/dist/integrations/customToolFactory.d.ts +13 -0
  153. package/dist/integrations/customToolFactory.d.ts.map +1 -0
  154. package/dist/integrations/customToolFactory.js +31 -0
  155. package/dist/integrations/customToolFactory.js.map +1 -0
  156. package/dist/integrations/dataLoader.d.ts +2 -2
  157. package/dist/integrations/dataLoader.d.ts.map +1 -1
  158. package/dist/integrations/dataLoader.js +1 -1
  159. package/dist/integrations/dataLoader.js.map +1 -1
  160. package/dist/integrations/fileIntegrationTypeConfigStore.d.ts +7 -0
  161. package/dist/integrations/fileIntegrationTypeConfigStore.d.ts.map +1 -0
  162. package/dist/integrations/fileIntegrationTypeConfigStore.js +34 -0
  163. package/dist/integrations/fileIntegrationTypeConfigStore.js.map +1 -0
  164. package/dist/integrations/getIntegration.d.ts +4 -1
  165. package/dist/integrations/getIntegration.d.ts.map +1 -1
  166. package/dist/integrations/getIntegration.js +12 -4
  167. package/dist/integrations/getIntegration.js.map +1 -1
  168. package/dist/integrations/health.d.ts +20 -0
  169. package/dist/integrations/health.d.ts.map +1 -0
  170. package/dist/integrations/health.js +43 -0
  171. package/dist/integrations/health.js.map +1 -0
  172. package/dist/integrations/integrationTypeConfigLookup.d.ts +12 -0
  173. package/dist/integrations/integrationTypeConfigLookup.d.ts.map +1 -0
  174. package/dist/integrations/integrationTypeConfigLookup.js +11 -0
  175. package/dist/integrations/integrationTypeConfigLookup.js.map +1 -0
  176. package/dist/integrations/providerRegistry.d.ts.map +1 -1
  177. package/dist/integrations/providerRegistry.js +0 -4
  178. package/dist/integrations/providerRegistry.js.map +1 -1
  179. package/dist/integrations/proxy.d.ts +4 -1
  180. package/dist/integrations/proxy.d.ts.map +1 -1
  181. package/dist/integrations/proxy.js +94 -106
  182. package/dist/integrations/proxy.js.map +1 -1
  183. package/dist/integrations/sandbox.js +11 -1
  184. package/dist/integrations/sandbox.js.map +1 -1
  185. package/dist/mcp/abilityCatalog.d.ts +46 -0
  186. package/dist/mcp/abilityCatalog.d.ts.map +1 -0
  187. package/dist/mcp/abilityCatalog.js +275 -0
  188. package/dist/mcp/abilityCatalog.js.map +1 -0
  189. package/dist/mcp/builder_guide.md +441 -0
  190. package/dist/mcp/commandable_readme.md +29 -0
  191. package/dist/mcp/handlers.d.ts +10 -1
  192. package/dist/mcp/handlers.d.ts.map +1 -1
  193. package/dist/mcp/handlers.js +51 -4
  194. package/dist/mcp/handlers.js.map +1 -1
  195. package/dist/mcp/metaTools.d.ts +77 -0
  196. package/dist/mcp/metaTools.d.ts.map +1 -0
  197. package/dist/mcp/metaTools.js +753 -0
  198. package/dist/mcp/metaTools.js.map +1 -0
  199. package/dist/mcp/server.d.ts +10 -0
  200. package/dist/mcp/server.d.ts.map +1 -1
  201. package/dist/mcp/server.js +2 -2
  202. package/dist/mcp/server.js.map +1 -1
  203. package/dist/mcp/sessionState.d.ts +18 -0
  204. package/dist/mcp/sessionState.d.ts.map +1 -0
  205. package/dist/mcp/sessionState.js +65 -0
  206. package/dist/mcp/sessionState.js.map +1 -0
  207. package/dist/mcp/toolAdapter.d.ts +17 -0
  208. package/dist/mcp/toolAdapter.d.ts.map +1 -1
  209. package/dist/mcp/toolAdapter.js +7 -1
  210. package/dist/mcp/toolAdapter.js.map +1 -1
  211. package/dist/types.d.ts +61 -2
  212. package/dist/types.d.ts.map +1 -1
  213. package/dist/version.d.ts +2 -0
  214. package/dist/version.d.ts.map +1 -0
  215. package/dist/version.js +7 -0
  216. package/dist/version.js.map +1 -0
  217. package/package.json +7 -5
package/dist/cli/index.js CHANGED
@@ -1,20 +1,247 @@
1
1
  import picocolors from 'picocolors';
2
2
  import crypto from 'node:crypto';
3
+ import { spawn, spawnSync } from 'node:child_process';
4
+ import { chmodSync, existsSync, readFileSync, unlinkSync, writeFileSync } from 'node:fs';
5
+ import { homedir } from 'node:os';
6
+ import { dirname, resolve } from 'node:path';
7
+ import { fileURLToPath } from 'node:url';
3
8
  import { IntegrationProxy } from '../integrations/proxy.js';
4
9
  import { buildMcpToolIndex } from '../mcp/toolAdapter.js';
5
10
  import { runStdioMcpServer } from '../mcp/server.js';
6
11
  import { createApiKey, generateApiKey } from '../mcp/auth.js';
12
+ import { AbilityCatalog } from '../mcp/abilityCatalog.js';
13
+ import { SessionAbilityState } from '../mcp/sessionState.js';
14
+ import { getBuilderToolDefinitions } from '../mcp/metaTools.js';
15
+ import { COMMANDABLE_VERSION } from '../version.js';
7
16
  import { runAddInteractive, runInitInteractive } from './setup.js';
8
- import { getOrCreateEncryptionSecret, openLocalState } from './credentialManager.js';
17
+ import { getCommandableDir, getOrCreateEncryptionSecret, openLocalState } from './credentialManager.js';
9
18
  import { listIntegrations } from '../db/integrationStore.js';
19
+ import { listToolDefinitions } from '../db/toolDefinitionStore.js';
20
+ import { listIntegrationTypeConfigs } from '../db/integrationTypeConfigStore.js';
10
21
  import { createDbFromEnv } from '../db/client.js';
11
22
  import { ensureSchema } from '../db/migrate.js';
12
23
  import { SqlCredentialStore } from '../db/credentialStore.js';
13
24
  import { applyConfig } from '../config/configApply.js';
14
25
  import { loadConfig } from '../config/configLoader.js';
26
+ import { integrationDataRoot } from '../integrations/dataLoader.js';
15
27
  function hasFlag(...flags) {
16
28
  return flags.some(f => process.argv.includes(f));
17
29
  }
30
+ function resolveMode() {
31
+ const explicit = (process.env.COMMANDABLE_MODE || '').toLowerCase().trim();
32
+ if (explicit === 'create')
33
+ return 'create';
34
+ return 'static';
35
+ }
36
+ async function fetchJsonWithTimeout(url, timeoutMs) {
37
+ const controller = new AbortController();
38
+ const t = setTimeout(() => controller.abort(), timeoutMs);
39
+ try {
40
+ const resp = await fetch(url, { signal: controller.signal });
41
+ const text = await resp.text();
42
+ let json = null;
43
+ try {
44
+ json = JSON.parse(text);
45
+ }
46
+ catch { }
47
+ return { ok: resp.ok, status: resp.status, json, text };
48
+ }
49
+ finally {
50
+ clearTimeout(t);
51
+ }
52
+ }
53
+ function expectedUiIdentity() {
54
+ const spaceId = (process.env.COMMANDABLE_SPACE_ID || 'local').trim() || 'local';
55
+ const databaseUrl = process.env.DATABASE_URL;
56
+ if (databaseUrl && databaseUrl.trim().length)
57
+ return { spaceId, db: { dialect: 'postgres' } };
58
+ const forced = process.env.COMMANDABLE_MCP_SQLITE_PATH;
59
+ if (forced && forced.trim().length)
60
+ return { spaceId, db: { dialect: 'sqlite', sqlitePath: resolve(forced.trim()) } };
61
+ const dataDir = process.env.COMMANDABLE_DATA_DIR;
62
+ const sqlitePath = dataDir && dataDir.trim().length
63
+ ? resolve(dataDir.trim(), 'credentials.sqlite')
64
+ : resolve(homedir(), '.commandable', 'credentials.sqlite');
65
+ return { spaceId, db: { dialect: 'sqlite', sqlitePath } };
66
+ }
67
+ async function waitForHttp(baseUrl, timeoutMs = 12_000) {
68
+ const start = Date.now();
69
+ while (Date.now() - start < timeoutMs) {
70
+ try {
71
+ const resp = await fetch(`${baseUrl}/api/_commandable/status`).catch(() => null);
72
+ if (resp && resp.ok) {
73
+ const data = await resp.json().catch(() => null);
74
+ if (data?.service === 'commandable-management-ui' && data?.ok === true)
75
+ return;
76
+ }
77
+ }
78
+ catch { }
79
+ await new Promise(resolve => setTimeout(resolve, 150));
80
+ }
81
+ throw new Error(`Timed out waiting for management UI at ${baseUrl}`);
82
+ }
83
+ async function startBundledManagementUi(params) {
84
+ const port = params.port;
85
+ const baseUrl = `http://127.0.0.1:${port}`;
86
+ // If something is already listening, only reuse it if it's our UI and points
87
+ // at the same local DB + space. Otherwise fail fast with a clear message to
88
+ // avoid sending users to the wrong instance.
89
+ try {
90
+ const probe = await fetchJsonWithTimeout(`${baseUrl}/api/_commandable/status`, 400);
91
+ if (probe?.ok) {
92
+ const status = probe.json;
93
+ if (status?.service !== 'commandable-management-ui' || status?.ok !== true) {
94
+ throw new Error(`Port ${port} is already in use by a non-Commandable service. ` +
95
+ `Set COMMANDABLE_UI_PORT to a different port or stop the process using ${baseUrl}.`);
96
+ }
97
+ const expected = expectedUiIdentity();
98
+ const sameSpace = status?.spaceId === expected.spaceId;
99
+ const sameDialect = status?.db?.dialect === expected.db.dialect;
100
+ const sameSqlite = status?.db?.dialect !== 'sqlite' || expected.db.dialect !== 'sqlite'
101
+ ? true
102
+ : status?.db?.sqlitePath === expected.db.sqlitePath;
103
+ if (!sameSpace || !sameDialect || !sameSqlite) {
104
+ const details = [
105
+ `expected spaceId=${expected.spaceId}, got=${status?.spaceId}`,
106
+ `expected dialect=${expected.db.dialect}, got=${status?.db?.dialect}`,
107
+ expected.db.dialect === 'sqlite' ? `expected sqlitePath=${expected.db.sqlitePath}` : null,
108
+ status?.db?.dialect === 'sqlite' ? `got sqlitePath=${status?.db?.sqlitePath}` : null,
109
+ ].filter(Boolean).join(' | ');
110
+ throw new Error(`A Commandable management UI is already running at ${baseUrl} but it does not match this session's DB/space (${details}). ` +
111
+ `Stop the existing UI process or set COMMANDABLE_UI_PORT to avoid conflicts.`);
112
+ }
113
+ const runningVersion = typeof status?.version === 'string' && status.version.trim().length ? status.version.trim() : null;
114
+ if (runningVersion !== COMMANDABLE_VERSION) {
115
+ console.error(`[commandable] restarting management UI due to version mismatch (running=${runningVersion ?? 'unknown'}, installed=${COMMANDABLE_VERSION})`);
116
+ const info = readDaemonPid();
117
+ if (info?.pid && isProcessAlive(info.pid)) {
118
+ try {
119
+ process.kill(info.pid, 'SIGTERM');
120
+ }
121
+ catch { }
122
+ try {
123
+ unlinkSync(daemonPidPath());
124
+ }
125
+ catch { }
126
+ // Wait briefly for the port to be released.
127
+ await new Promise(resolve => setTimeout(resolve, 250));
128
+ }
129
+ else {
130
+ throw new Error(`A Commandable management UI is running at ${baseUrl} with version=${runningVersion ?? 'unknown'}, but the daemon PID could not be determined. ` +
131
+ `Stop the process using ${baseUrl} (or change COMMANDABLE_UI_PORT) to continue.`);
132
+ }
133
+ }
134
+ console.error(`[commandable] reusing existing management UI at ${baseUrl}`);
135
+ return { baseUrl, stop: async () => { }, reused: true };
136
+ }
137
+ else if (probe?.status && probe.status !== 404) {
138
+ throw new Error(`Port ${port} is already in use (HTTP ${probe.status}). ` +
139
+ `Set COMMANDABLE_UI_PORT to a different port or stop the process using ${baseUrl}.`);
140
+ }
141
+ }
142
+ catch {
143
+ // Connection refused / not reachable -> port is likely free; proceed to spawn.
144
+ }
145
+ const here = dirname(fileURLToPath(import.meta.url));
146
+ // dist/cli/index.js -> dist/app/server/index.mjs
147
+ const serverEntry = resolve(here, '..', 'app', 'server', 'index.mjs');
148
+ const cwd = resolve(here, '..', 'app');
149
+ if (!existsSync(serverEntry)) {
150
+ console.error(`[commandable] bundled management UI not found at: ${serverEntry}`);
151
+ console.error(`[commandable] create-mode will run without the UI; credentials links will be unavailable.`);
152
+ return null;
153
+ }
154
+ const execPath = existsSync(process.execPath) ? process.execPath : 'node';
155
+ const child = spawn(execPath, [serverEntry], {
156
+ cwd,
157
+ env: {
158
+ ...process.env,
159
+ HOST: '127.0.0.1',
160
+ PORT: String(port),
161
+ COMMANDABLE_MODE: 'create',
162
+ COMMANDABLE_VERSION,
163
+ COMMANDABLE_INTEGRATION_DATA_DIR: process.env.COMMANDABLE_INTEGRATION_DATA_DIR || integrationDataRoot(),
164
+ COMMANDABLE_MCP_SQLITE_PATH: process.env.COMMANDABLE_MCP_SQLITE_PATH || resolve(getCommandableDir(), 'credentials.sqlite'),
165
+ },
166
+ stdio: 'ignore',
167
+ detached: true,
168
+ });
169
+ child.unref();
170
+ const pidPath = resolve(getCommandableDir(), 'daemon.pid');
171
+ try {
172
+ // Format:
173
+ // <pid>
174
+ // <version>
175
+ writeFileSync(pidPath, `${child.pid}\n${COMMANDABLE_VERSION}\n`, { mode: 0o600 });
176
+ try {
177
+ chmodSync(pidPath, 0o600);
178
+ }
179
+ catch { }
180
+ }
181
+ catch { }
182
+ try {
183
+ await waitForHttp(baseUrl);
184
+ }
185
+ catch (err) {
186
+ console.error(`[commandable] management UI failed to start at ${baseUrl}`);
187
+ console.error(err?.message || err);
188
+ // Daemon start failed; attempt cleanup (best effort).
189
+ try {
190
+ process.kill(child.pid, 'SIGTERM');
191
+ }
192
+ catch { }
193
+ return null;
194
+ }
195
+ return {
196
+ baseUrl,
197
+ reused: false,
198
+ stop: async () => {
199
+ const pidFile = resolve(getCommandableDir(), 'daemon.pid');
200
+ let pid = null;
201
+ try {
202
+ const raw = readFileSync(pidFile, 'utf8');
203
+ const first = raw.split('\n')[0]?.trim() || '';
204
+ pid = first && /^\d+$/.test(first) ? Number(first) : null;
205
+ }
206
+ catch { }
207
+ if (pid) {
208
+ try {
209
+ process.kill(pid, 'SIGTERM');
210
+ }
211
+ catch { }
212
+ }
213
+ try {
214
+ unlinkSync(pidFile);
215
+ }
216
+ catch { }
217
+ },
218
+ };
219
+ }
220
+ function daemonPidPath() {
221
+ return resolve(getCommandableDir(), 'daemon.pid');
222
+ }
223
+ function readDaemonPid() {
224
+ try {
225
+ const raw = readFileSync(daemonPidPath(), 'utf8');
226
+ const lines = raw.split('\n').map(l => l.trim()).filter(Boolean);
227
+ const pidRaw = lines[0] || '';
228
+ const versionRaw = lines[1] || null;
229
+ const pid = pidRaw && /^\d+$/.test(pidRaw) ? Number(pidRaw) : null;
230
+ return pid ? { pid, version: versionRaw } : null;
231
+ }
232
+ catch {
233
+ return null;
234
+ }
235
+ }
236
+ function isProcessAlive(pid) {
237
+ try {
238
+ process.kill(pid, 0);
239
+ return true;
240
+ }
241
+ catch {
242
+ return false;
243
+ }
244
+ }
18
245
  function getFlagValue(flag) {
19
246
  const idx = process.argv.indexOf(flag);
20
247
  if (idx === -1)
@@ -24,44 +251,385 @@ function getFlagValue(flag) {
24
251
  return null;
25
252
  return val;
26
253
  }
254
+ function getUiPort() {
255
+ const uiPortRaw = process.env.COMMANDABLE_UI_PORT;
256
+ return uiPortRaw && /^\d+$/.test(uiPortRaw) ? Number(uiPortRaw) : 23432;
257
+ }
258
+ function getSqlitePathForLocalState() {
259
+ const forced = process.env.COMMANDABLE_MCP_SQLITE_PATH;
260
+ if (forced && forced.trim().length)
261
+ return resolve(forced.trim());
262
+ return resolve(getCommandableDir(), 'credentials.sqlite');
263
+ }
264
+ function getSourceValue() {
265
+ const raw = (getFlagValue('--source') || 'package').trim().toLowerCase();
266
+ if (raw === 'local' || raw === 'package')
267
+ return raw;
268
+ console.error(`Invalid --source value: ${raw}. Use ${picocolors.cyan('package')} or ${picocolors.cyan('local')}.`);
269
+ process.exit(1);
270
+ }
271
+ function getTransportValue() {
272
+ const raw = (getFlagValue('--transport') || 'stdio').trim().toLowerCase();
273
+ if (raw === 'stdio' || raw === 'http')
274
+ return raw;
275
+ console.error(`Invalid --transport value: ${raw}. Use ${picocolors.cyan('stdio')} or ${picocolors.cyan('http')}.`);
276
+ process.exit(1);
277
+ }
278
+ function getReadClientValue() {
279
+ const raw = (getFlagValue('--client') || 'claude-desktop').trim().toLowerCase();
280
+ if (raw === 'claude-desktop' || raw === 'cursor')
281
+ return raw;
282
+ console.error(`Invalid --client value: ${raw}. Use ${picocolors.cyan('claude-desktop')} or ${picocolors.cyan('cursor')}.`);
283
+ process.exit(1);
284
+ }
285
+ function stopDaemonProcess() {
286
+ const pid = readDaemonPid();
287
+ if (pid?.pid) {
288
+ try {
289
+ process.kill(pid.pid, 'SIGTERM');
290
+ }
291
+ catch { }
292
+ }
293
+ try {
294
+ unlinkSync(daemonPidPath());
295
+ }
296
+ catch { }
297
+ return { stopped: !!pid?.pid, pid: pid?.pid ?? null };
298
+ }
299
+ export function makeClaudeCodeAddCommand(source) {
300
+ const envArgs = getClaudeCodeEnvEntries()
301
+ .map(value => `-e ${quoteShellArg(value)}`)
302
+ .join(' ');
303
+ if (source === 'package')
304
+ return `claude mcp add commandable${envArgs ? ` ${envArgs}` : ''} -- npx -y @commandable/mcp create-mode`;
305
+ const localBin = process.argv[1] && process.argv[1].trim().length
306
+ ? resolve(process.argv[1])
307
+ : '/absolute/path/to/commandable-mcp/packages/server/dist/cli/bin.js';
308
+ return `claude mcp add commandable${envArgs ? ` ${envArgs}` : ''} -- node ${localBin} create-mode`;
309
+ }
310
+ function execClaudeMcpAdd(args) {
311
+ const result = spawnSync('claude', args, { stdio: 'inherit' });
312
+ if (result.error) {
313
+ // claude CLI not available — print the command as fallback
314
+ console.error(`claude ${args.join(' ')}`);
315
+ return;
316
+ }
317
+ if (result.status !== 0)
318
+ process.exit(result.status ?? 1);
319
+ }
320
+ const CLAUDE_CODE_STDIO_ENV_KEYS = [
321
+ 'COMMANDABLE_SPACE_ID',
322
+ 'COMMANDABLE_DATA_DIR',
323
+ 'COMMANDABLE_MCP_SQLITE_PATH',
324
+ 'COMMANDABLE_UI_PORT',
325
+ 'DATABASE_URL',
326
+ 'COMMANDABLE_CONFIG_FILE',
327
+ 'COMMANDABLE_INTEGRATION_DATA_DIR',
328
+ ];
329
+ function getClaudeCodeEnvEntries() {
330
+ return CLAUDE_CODE_STDIO_ENV_KEYS
331
+ .map((key) => {
332
+ const value = process.env[key];
333
+ return value && value.trim().length ? `${key}=${value}` : null;
334
+ })
335
+ .filter((value) => !!value);
336
+ }
337
+ function quoteShellArg(value) {
338
+ if (/^[A-Za-z0-9_./:=@-]+$/.test(value))
339
+ return value;
340
+ return `'${value.replace(/'/g, `'\"'\"'`)}'`;
341
+ }
342
+ export function makeReadModeConfig(source) {
343
+ if (source === 'package') {
344
+ return {
345
+ mcpServers: {
346
+ commandable: {
347
+ command: 'npx',
348
+ args: ['-y', '@commandable/mcp'],
349
+ },
350
+ },
351
+ };
352
+ }
353
+ const localBin = process.argv[1] && process.argv[1].trim().length
354
+ ? resolve(process.argv[1])
355
+ : '/absolute/path/to/commandable-mcp/packages/server/dist/cli/bin.js';
356
+ return {
357
+ mcpServers: {
358
+ commandable: {
359
+ command: 'node',
360
+ args: [localBin],
361
+ },
362
+ },
363
+ };
364
+ }
365
+ export function makeHttpConnectionDetails() {
366
+ const url = getFlagValue('--url') || 'https://your-host/mcp';
367
+ const apiKey = getFlagValue('--api-key') || '<api-key>';
368
+ return {
369
+ url,
370
+ headers: {
371
+ Authorization: `Bearer ${apiKey}`,
372
+ },
373
+ };
374
+ }
375
+ function makeClaudeCodeHttpAddCommand() {
376
+ const details = makeHttpConnectionDetails();
377
+ return `claude mcp add --transport http commandable ${details.url} --header "Authorization: ${details.headers.Authorization}"`;
378
+ }
379
+ function runCreate() {
380
+ const source = getSourceValue();
381
+ const transport = getTransportValue();
382
+ const shouldApply = hasFlag('--apply');
383
+ const command = transport === 'http'
384
+ ? makeClaudeCodeHttpAddCommand()
385
+ : makeClaudeCodeAddCommand(source);
386
+ if (!shouldApply) {
387
+ console.error(command);
388
+ console.error('Run that, then restart Claude Code.');
389
+ return;
390
+ }
391
+ if (transport === 'http') {
392
+ const url = getFlagValue('--url');
393
+ const apiKey = getFlagValue('--api-key');
394
+ if (!url || !apiKey) {
395
+ console.error(`For ${picocolors.cyan('--apply')} with HTTP, pass ${picocolors.cyan('--url')} and ${picocolors.cyan('--api-key')}.`);
396
+ process.exit(1);
397
+ }
398
+ execClaudeMcpAdd(['mcp', 'add', '--transport', 'http', 'commandable', url, '--header', `Authorization: Bearer ${apiKey}`]);
399
+ console.error(`${picocolors.green('Done.')} Restart Claude Code.`);
400
+ return;
401
+ }
402
+ const envArgs = getClaudeCodeEnvEntries().flatMap(value => ['-e', value]);
403
+ if (source === 'package')
404
+ execClaudeMcpAdd(['mcp', 'add', 'commandable', ...envArgs, '--', 'npx', '-y', '@commandable/mcp', 'create-mode']);
405
+ else {
406
+ const localBin = process.argv[1] && process.argv[1].trim().length
407
+ ? resolve(process.argv[1])
408
+ : '/absolute/path/to/commandable-mcp/packages/server/dist/cli/bin.js';
409
+ execClaudeMcpAdd(['mcp', 'add', 'commandable', ...envArgs, '--', 'node', localBin, 'create-mode']);
410
+ }
411
+ console.error(`${picocolors.green('Done.')} Restart Claude Code.`);
412
+ }
413
+ async function runServeLocal(opts) {
414
+ const uiPort = getUiPort();
415
+ const baseUrl = `http://127.0.0.1:${uiPort}`;
416
+ if (opts.restart)
417
+ stopDaemonProcess();
418
+ // Run migrations once here before the app server starts. Both the app server
419
+ // and create-mode open the DB without running migrations themselves, so this
420
+ // is the single authoritative place schema is applied.
421
+ const migrateClient = createDbFromEnv();
422
+ await ensureSchema(migrateClient);
423
+ migrateClient.close();
424
+ const ui = await startBundledManagementUi({ port: uiPort });
425
+ if (!ui)
426
+ throw new Error(`Failed to ${opts.restart ? 'restart' : 'start'} local Commandable instance`);
427
+ const sqlitePath = getSqlitePathForLocalState();
428
+ console.error(picocolors.green(`Commandable local instance ${ui.reused && !opts.restart ? 'ready' : 'running'}.`));
429
+ console.error(`${picocolors.dim('Base URL:')} ${baseUrl}`);
430
+ console.error(`${picocolors.dim('Management UI:')} ${baseUrl}/`);
431
+ console.error(`${picocolors.dim('MCP endpoint:')} ${baseUrl}/mcp`);
432
+ console.error(`${picocolors.dim('Data dir:')} ${getCommandableDir()}`);
433
+ console.error(`${picocolors.dim('SQLite:')} ${sqlitePath}`);
434
+ console.error(`${picocolors.dim('State:')} ${ui.reused ? 'reused existing instance' : 'started fresh instance'}`);
435
+ console.error(`Next: ${picocolors.cyan('commandable-mcp create')} or ${picocolors.cyan('commandable-mcp connect --client claude-desktop')}`);
436
+ }
437
+ async function runServe() {
438
+ const transport = getFlagValue('--transport');
439
+ if (transport && transport.trim().toLowerCase() === 'http') {
440
+ console.error('HTTP deployment is served by the app runtime, not this CLI.');
441
+ console.error(`Use ${picocolors.cyan('yarn dev')} locally or deploy the app, then run ${picocolors.cyan('commandable-mcp create --transport http --url <url> --api-key <key>')}.`);
442
+ return;
443
+ }
444
+ return await runServeLocal({ restart: hasFlag('--restart') });
445
+ }
446
+ function runConnect() {
447
+ const source = getSourceValue();
448
+ const transport = getTransportValue();
449
+ const client = getReadClientValue();
450
+ if (transport === 'http') {
451
+ console.error(JSON.stringify(makeHttpConnectionDetails(), null, 2));
452
+ return;
453
+ }
454
+ if (client === 'cursor' || client === 'claude-desktop') {
455
+ console.error(JSON.stringify(makeReadModeConfig(source), null, 2));
456
+ }
457
+ }
458
+ async function runDoctor() {
459
+ const uiPort = getUiPort();
460
+ const baseUrl = `http://127.0.0.1:${uiPort}`;
461
+ const pid = readDaemonPid();
462
+ const pidAlive = pid ? isProcessAlive(pid.pid) : false;
463
+ const probe = await fetchJsonWithTimeout(`${baseUrl}/api/_commandable/status`, 500).catch(() => null);
464
+ const probeVersion = typeof probe?.json?.version === 'string' && probe.json.version.trim().length
465
+ ? probe.json.version.trim()
466
+ : null;
467
+ const runningVersion = probeVersion || pid?.version || null;
468
+ const databaseUrl = process.env.DATABASE_URL;
469
+ const sqlitePath = databaseUrl && databaseUrl.trim().length ? null : getSqlitePathForLocalState();
470
+ const report = {
471
+ ok: true,
472
+ installedVersion: COMMANDABLE_VERSION,
473
+ mode: resolveMode(),
474
+ env: {
475
+ COMMANDABLE_SPACE_ID: (process.env.COMMANDABLE_SPACE_ID || 'local').trim() || 'local',
476
+ COMMANDABLE_DATA_DIR: process.env.COMMANDABLE_DATA_DIR || null,
477
+ COMMANDABLE_MCP_SQLITE_PATH: process.env.COMMANDABLE_MCP_SQLITE_PATH || null,
478
+ COMMANDABLE_UI_PORT: uiPort,
479
+ DATABASE_URL: databaseUrl && databaseUrl.trim().length ? '[set]' : null,
480
+ },
481
+ localState: {
482
+ dataDir: getCommandableDir(),
483
+ sqlitePath,
484
+ daemonPidPath: daemonPidPath(),
485
+ encryptionKeyPath: resolve(getCommandableDir(), 'encryption.key'),
486
+ },
487
+ daemon: {
488
+ running: !!(pidAlive && probe?.ok),
489
+ pid: pid ?? null,
490
+ baseUrl,
491
+ runningVersion,
492
+ versionMatch: !!runningVersion && runningVersion === COMMANDABLE_VERSION,
493
+ status: probe?.json ?? null,
494
+ },
495
+ hints: [
496
+ 'Use "commandable-mcp reset local --yes" for a full local wipe.',
497
+ 'Use "commandable-mcp serve" to start the local Commandable instance.',
498
+ 'Use "commandable-mcp create" for the Claude Code authoring flow.',
499
+ 'Use "commandable-mcp connect --client claude-desktop" for a read-client config snippet.',
500
+ ],
501
+ };
502
+ console.error(JSON.stringify(report, null, 2));
503
+ }
504
+ function runResetLocal() {
505
+ const confirm = hasFlag('--yes');
506
+ const keepKey = hasFlag('--keep-key');
507
+ if (!confirm) {
508
+ console.error(`This command deletes local Commandable state.`);
509
+ console.error(`Re-run with ${picocolors.cyan('--yes')} to confirm.`);
510
+ process.exit(1);
511
+ }
512
+ const dataDir = getCommandableDir();
513
+ const sqlitePath = getSqlitePathForLocalState();
514
+ const keyPath = resolve(dataDir, 'encryption.key');
515
+ const daemonPath = daemonPidPath();
516
+ const stopped = stopDaemonProcess();
517
+ const removed = [];
518
+ const missing = [];
519
+ for (const file of [sqlitePath, daemonPath, ...(keepKey ? [] : [keyPath])]) {
520
+ if (!existsSync(file)) {
521
+ missing.push(file);
522
+ continue;
523
+ }
524
+ try {
525
+ unlinkSync(file);
526
+ removed.push(file);
527
+ }
528
+ catch (err) {
529
+ console.error(`Failed to remove ${file}: ${err?.message || err}`);
530
+ process.exit(1);
531
+ }
532
+ }
533
+ console.error(picocolors.green('Local reset complete.'));
534
+ console.error(`${picocolors.dim('Data dir:')} ${dataDir}`);
535
+ console.error(`${picocolors.dim('Daemon stopped:')} ${stopped.stopped ? 'yes' : 'no (no PID found)'}`);
536
+ if (removed.length)
537
+ console.error(`${picocolors.dim('Removed:')} ${removed.join(', ')}`);
538
+ if (missing.length)
539
+ console.error(`${picocolors.dim('Already absent:')} ${missing.join(', ')}`);
540
+ if (process.env.DATABASE_URL && process.env.DATABASE_URL.trim().length)
541
+ console.error(`${picocolors.yellow('Warning:')} DATABASE_URL is set; remote Postgres data was not modified.`);
542
+ console.error(`Next step: ${picocolors.cyan('commandable-mcp create')}`);
543
+ console.error(`Legacy bootstrap: ${picocolors.cyan('commandable-mcp static-init')}`);
544
+ }
545
+ async function runRefreshLocalDev() {
546
+ await runServeLocal({ restart: true });
547
+ }
27
548
  function help(exitCode = 0) {
28
549
  const lines = [
29
550
  '',
30
- `${picocolors.bold('Commandable MCP')} — connect your apps to MCP clients`,
551
+ `${picocolors.bold('Commandable MCP')} — build and serve app-connected MCP tools`,
552
+ picocolors.dim(`v${COMMANDABLE_VERSION}`),
31
553
  '',
32
554
  picocolors.bold('Usage'),
33
- ` ${picocolors.cyan('commandable-mcp init')}`,
34
- ` ${picocolors.cyan('commandable-mcp init')} ${picocolors.dim('--config ./commandable.config.yaml')}`,
35
- ` ${picocolors.cyan('commandable-mcp add')}`,
36
- ` ${picocolors.cyan('commandable-mcp status')}`,
555
+ ` ${picocolors.cyan('commandable-mcp serve')} ${picocolors.dim('[--restart]')}`,
556
+ ` ${picocolors.cyan('commandable-mcp create')} ${picocolors.dim('[--transport stdio|http] [--source package|local] [--apply] [--url] [--api-key]')}`,
557
+ ` ${picocolors.cyan('commandable-mcp connect')} ${picocolors.dim('[--client claude-desktop|cursor] [--transport stdio|http] [--source package|local] [--url] [--api-key]')}`,
558
+ ` ${picocolors.cyan('commandable-mcp doctor')}`,
559
+ ` ${picocolors.cyan('commandable-mcp reset local')} ${picocolors.dim('[--yes] [--keep-key]')}`,
37
560
  ` ${picocolors.cyan('commandable-mcp apply')} ${picocolors.dim('[--config ./commandable.config.yaml]')}`,
38
561
  ` ${picocolors.cyan('commandable-mcp create-api-key')} ${picocolors.dim('[name]')}`,
39
- ` ${picocolors.cyan('commandable-mcp')} ${picocolors.dim('(start MCP server via stdio)')}`,
562
+ ` ${picocolors.cyan('commandable-mcp --version')}`,
40
563
  '',
41
564
  picocolors.bold('Notes'),
42
565
  `- Credentials entered via the CLI are stored encrypted at ${picocolors.dim('~/.commandable/')} (override with ${picocolors.cyan('COMMANDABLE_DATA_DIR')}).`,
43
- `- MCP clients (Claude Desktop, Cursor) spawn this server process automatically via: ${picocolors.cyan('npx -y @commandable/mcp')}`,
566
+ `- ${picocolors.bold('Serve')}: starts or reuses the local Commandable instance.`,
567
+ `- ${picocolors.bold('Create')}: Claude Code authoring flow. Prints or applies the ${picocolors.cyan('claude mcp add')} command.`,
568
+ `- ${picocolors.bold('Connect')}: prints read-client connection details for the MCP server you already configured.`,
44
569
  '',
45
570
  ];
46
571
  console.error(lines.join('\n'));
47
572
  process.exit(exitCode);
48
573
  }
49
- async function runStdioFromDb() {
574
+ async function runStdioFromDb(forceMode) {
50
575
  const spaceId = process.env.COMMANDABLE_SPACE_ID || 'local';
51
576
  const { db, credentialStore } = await openLocalState();
52
577
  const integrations = await listIntegrations(db, spaceId);
53
- if (!integrations.length) {
54
- console.error(`No integrations configured yet. Run ${picocolors.cyan('commandable-mcp init')}.`);
55
- process.exit(0);
56
- }
578
+ const toolDefinitions = await listToolDefinitions(db, spaceId);
579
+ const integrationTypeConfigs = await listIntegrationTypeConfigs(db, spaceId);
580
+ const integrationTypeConfigsRef = { current: integrationTypeConfigs };
57
581
  const proxy = new IntegrationProxy({
58
582
  credentialStore,
59
583
  trelloApiKey: process.env.TRELLO_API_KEY,
584
+ integrationTypeConfigsRef,
60
585
  });
61
- const index = buildMcpToolIndex({ spaceId, integrations, proxy });
586
+ const mode = forceMode ?? resolveMode();
587
+ if (!integrations.length && mode !== 'create') {
588
+ console.error(`No integrations configured yet.`);
589
+ console.error(`Preferred: ${picocolors.cyan('commandable-mcp create')} then configure tools in Claude Code.`);
590
+ console.error(`Legacy read-mode bootstrap: ${picocolors.cyan('commandable-mcp static-init')}.`);
591
+ process.exit(0);
592
+ }
593
+ const integrationsRef = { current: integrations };
594
+ const index = buildMcpToolIndex({ spaceId, integrations, proxy, integrationsRef, toolDefinitions });
595
+ const toolIndex = { list: index.tools, byName: index.byName };
596
+ const uiPortRaw = process.env.COMMANDABLE_UI_PORT;
597
+ const uiPort = uiPortRaw && /^\d+$/.test(uiPortRaw) ? Number(uiPortRaw) : 23432;
598
+ const managementUi = mode === 'create'
599
+ ? await startBundledManagementUi({ port: uiPort })
600
+ : null;
601
+ console.error(`[commandable] v${COMMANDABLE_VERSION}`);
62
602
  await runStdioMcpServer({
63
- serverInfo: { name: 'commandable', version: '0.0.1' },
64
- tools: { list: index.tools, byName: index.byName },
603
+ serverInfo: { name: 'commandable', version: COMMANDABLE_VERSION },
604
+ tools: toolIndex,
605
+ ...(mode === 'create'
606
+ ? {
607
+ createMode: (() => {
608
+ const builderDefs = getBuilderToolDefinitions();
609
+ const extraToolDefinitions = new Map(builderDefs.map(d => [d.name, d]));
610
+ const catalogRef = {
611
+ current: new AbilityCatalog({
612
+ integrations: integrationsRef.current,
613
+ toolIndex: toolIndex.byName,
614
+ extraToolDefinitions,
615
+ }),
616
+ };
617
+ const sessionState = new SessionAbilityState();
618
+ const ctx = {
619
+ spaceId,
620
+ db,
621
+ credentialStore,
622
+ proxy,
623
+ credentialSetupBaseUrl: managementUi?.baseUrl,
624
+ integrationsRef,
625
+ integrationTypeConfigsRef,
626
+ toolIndexRef: toolIndex,
627
+ catalogRef,
628
+ };
629
+ return { catalogRef, sessionState, ctx };
630
+ })(),
631
+ }
632
+ : {}),
65
633
  });
66
634
  }
67
635
  async function openEnvState() {
@@ -116,9 +684,19 @@ async function runCreateApiKey() {
116
684
  }
117
685
  export async function main() {
118
686
  const cmd = process.argv[2];
687
+ if (hasFlag('--version', '-v')) {
688
+ console.error(COMMANDABLE_VERSION);
689
+ process.exit(0);
690
+ }
119
691
  if (hasFlag('--help', '-h'))
120
692
  help(0);
121
- if (cmd === 'init') {
693
+ if (cmd === 'serve')
694
+ return await runServe();
695
+ if (cmd === 'create')
696
+ return runCreate();
697
+ if (cmd === 'connect')
698
+ return runConnect();
699
+ if (cmd === 'static-init') {
122
700
  const cfgPath = getFlagValue('--config') || process.env.COMMANDABLE_CONFIG_FILE || null;
123
701
  if (cfgPath)
124
702
  return await runApplyHeadless();
@@ -145,8 +723,69 @@ export async function main() {
145
723
  }
146
724
  if (cmd === 'apply')
147
725
  return await runApplyHeadless();
726
+ if (cmd === 'doctor')
727
+ return await runDoctor();
728
+ if (cmd === 'reset') {
729
+ const sub = (process.argv[3] || '').trim().toLowerCase();
730
+ if (sub === 'local')
731
+ return runResetLocal();
732
+ help(1);
733
+ }
734
+ if (cmd === 'setup') {
735
+ const sub = (process.argv[3] || '').trim().toLowerCase();
736
+ if (sub === 'claude-code')
737
+ return runCreate();
738
+ help(1);
739
+ }
740
+ if (cmd === 'refresh') {
741
+ const sub = (process.argv[3] || '').trim().toLowerCase();
742
+ if (sub === 'local-dev')
743
+ return await runRefreshLocalDev();
744
+ help(1);
745
+ }
148
746
  if (cmd === 'create-api-key')
149
747
  return await runCreateApiKey();
748
+ if (cmd === 'create-mode')
749
+ return await runStdioFromDb('create');
750
+ if (cmd === 'daemon') {
751
+ const sub = (process.argv[3] || '').trim().toLowerCase();
752
+ const uiPort = getUiPort();
753
+ const baseUrl = `http://127.0.0.1:${uiPort}`;
754
+ if (sub === 'status' || !sub) {
755
+ const pid = readDaemonPid();
756
+ const alive = pid ? isProcessAlive(pid.pid) : false;
757
+ const probe = await fetchJsonWithTimeout(`${baseUrl}/api/_commandable/status`, 400).catch(() => null);
758
+ const ok = !!probe?.ok;
759
+ const probeVersion = typeof probe?.json?.version === 'string' && probe.json.version.trim().length ? probe.json.version.trim() : null;
760
+ const runningVersion = probeVersion || pid?.version || null;
761
+ console.error(JSON.stringify({
762
+ running: alive && ok,
763
+ pid,
764
+ baseUrl,
765
+ installedVersion: COMMANDABLE_VERSION,
766
+ runningVersion,
767
+ versionMatch: !!runningVersion && runningVersion === COMMANDABLE_VERSION,
768
+ status: probe?.json ?? null,
769
+ }, null, 2));
770
+ return;
771
+ }
772
+ if (sub === 'stop') {
773
+ const stopped = stopDaemonProcess();
774
+ console.error(stopped.stopped
775
+ ? `Stopped daemon${stopped.pid ? ` (pid ${stopped.pid})` : ''}.`
776
+ : 'No daemon PID found; nothing to stop.');
777
+ return;
778
+ }
779
+ if (sub === 'start') {
780
+ await runServeLocal({ restart: false });
781
+ return;
782
+ }
783
+ if (sub === 'restart') {
784
+ await runServeLocal({ restart: true });
785
+ return;
786
+ }
787
+ help(1);
788
+ }
150
789
  if (cmd && !cmd.startsWith('-'))
151
790
  help(1);
152
791
  return await runStdioFromDb();