crewx 0.8.1 → 0.8.2-rc.2

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 (125) hide show
  1. package/README.md +268 -268
  2. package/bin/cli-commands.js +34 -0
  3. package/bin/crewx-lib.js +213 -108
  4. package/bin/crewx-ui.js +83 -83
  5. package/bin/crewx.js +219 -147
  6. package/bin/launcher-flags.js +29 -0
  7. package/bin/package.json +1 -1
  8. package/dist/assets/MarketPage-DptjaFpT.js +36 -0
  9. package/dist/assets/{PromptTab-DVKc7hJY.js → PromptTab-DZha2_v1.js} +1 -1
  10. package/dist/assets/{_baseUniq-wjlVo2E6.js → _baseUniq-jd6NubI3.js} +1 -1
  11. package/dist/assets/{arc-BfPgRtzW.js → arc-C2te3-8P.js} +1 -1
  12. package/dist/assets/{architectureDiagram-Q4EWVU46-ewcueFAG.js → architectureDiagram-Q4EWVU46-CbIQua02.js} +1 -1
  13. package/dist/assets/{blockDiagram-DXYQGD6D-TxlbbvKn.js → blockDiagram-DXYQGD6D-Cg7qkpSM.js} +1 -1
  14. package/dist/assets/{c4Diagram-AHTNJAMY-C1lT_bl_.js → c4Diagram-AHTNJAMY-BkffDY1F.js} +1 -1
  15. package/dist/assets/channel-beae0DeI.js +1 -0
  16. package/dist/assets/chatgpt-logo-dark.svg +15 -15
  17. package/dist/assets/chatgpt-logo.svg +15 -15
  18. package/dist/assets/{chunk-4BX2VUAB-C41j2mCL.js → chunk-4BX2VUAB-BxHe9wPE.js} +1 -1
  19. package/dist/assets/{chunk-4TB4RGXK-HNNsUbz0.js → chunk-4TB4RGXK--f1tN90O.js} +1 -1
  20. package/dist/assets/{chunk-55IACEB6-qtCgO0r2.js → chunk-55IACEB6-B5QXfPXQ.js} +1 -1
  21. package/dist/assets/{chunk-EDXVE4YY-BSnDYtsd.js → chunk-EDXVE4YY-DqKhblOg.js} +1 -1
  22. package/dist/assets/{chunk-FMBD7UC4-DyHRLQqX.js → chunk-FMBD7UC4-DZ1w_G02.js} +1 -1
  23. package/dist/assets/{chunk-OYMX7WX6-CCjfi6WS.js → chunk-OYMX7WX6-BqAgQpv8.js} +1 -1
  24. package/dist/assets/{chunk-QZHKN3VN-COLty8kd.js → chunk-QZHKN3VN-DPqnGqVi.js} +1 -1
  25. package/dist/assets/{chunk-YZCP3GAM-CHUUnGeN.js → chunk-YZCP3GAM-x-ZYSQLd.js} +1 -1
  26. package/dist/assets/classDiagram-6PBFFD2Q-BquWrs1y.js +1 -0
  27. package/dist/assets/classDiagram-v2-HSJHXN6E-BquWrs1y.js +1 -0
  28. package/dist/assets/clone-C9wSPtDN.js +1 -0
  29. package/dist/assets/{cose-bilkent-S5V4N54A-CSip-V2g.js → cose-bilkent-S5V4N54A-4VCNRP-E.js} +1 -1
  30. package/dist/assets/{dagre-KV5264BT-DkdpnWhv.js → dagre-KV5264BT-DucVi1QS.js} +1 -1
  31. package/dist/assets/{diagram-5BDNPKRD-PH4qc6PV.js → diagram-5BDNPKRD-ChdRA8bE.js} +1 -1
  32. package/dist/assets/{diagram-G4DWMVQ6-Cg5xZcjx.js → diagram-G4DWMVQ6-B1-97yHr.js} +1 -1
  33. package/dist/assets/{diagram-MMDJMWI5-soKmeTCW.js → diagram-MMDJMWI5-CQs3cO7G.js} +1 -1
  34. package/dist/assets/{diagram-TYMM5635-Daq5Mihu.js → diagram-TYMM5635-CuNCxDfO.js} +1 -1
  35. package/dist/assets/{erDiagram-SMLLAGMA-kr2OtY0Y.js → erDiagram-SMLLAGMA-DdR8v8g-.js} +1 -1
  36. package/dist/assets/{flowDiagram-DWJPFMVM-DQZCb8gm.js → flowDiagram-DWJPFMVM-Dt02upId.js} +1 -1
  37. package/dist/assets/{ganttDiagram-T4ZO3ILL-BHkn485T.js → ganttDiagram-T4ZO3ILL-cG_k9VOa.js} +1 -1
  38. package/dist/assets/{gitGraphDiagram-UUTBAWPF-FaCyYFmC.js → gitGraphDiagram-UUTBAWPF-Dz1DjhKq.js} +1 -1
  39. package/dist/assets/{graph-BVJlrP6V.js → graph-CF2NtM33.js} +1 -1
  40. package/dist/assets/{infoDiagram-42DDH7IO-DJOWkKdM.js → infoDiagram-42DDH7IO-tQWKrYM6.js} +1 -1
  41. package/dist/assets/{ishikawaDiagram-UXIWVN3A-VfpvNaIf.js → ishikawaDiagram-UXIWVN3A-CLuUMkF0.js} +1 -1
  42. package/dist/assets/{journeyDiagram-VCZTEJTY-CPzsak-v.js → journeyDiagram-VCZTEJTY-a6JenLCk.js} +1 -1
  43. package/dist/assets/{kanban-definition-6JOO6SKY-DFqLDBU0.js → kanban-definition-6JOO6SKY-rqOxTzYb.js} +1 -1
  44. package/dist/assets/{layout-CCSbNPHm.js → layout-Dvic1Hpy.js} +1 -1
  45. package/dist/assets/{linear-C4T7PCKE.js → linear-CfMV1S6a.js} +1 -1
  46. package/dist/assets/main-05K4ggqd.css +10 -0
  47. package/dist/assets/main-CCM1gtr8.js +1165 -0
  48. package/dist/assets/{min-CGQNEYGh.js → min-GpF3DZux.js} +1 -1
  49. package/dist/assets/{mindmap-definition-QFDTVHPH-AuU1EqwS.js → mindmap-definition-QFDTVHPH-Cg80z0Jx.js} +1 -1
  50. package/dist/assets/{pieDiagram-DEJITSTG-CopkCZwp.js → pieDiagram-DEJITSTG-BrtK7lAq.js} +1 -1
  51. package/dist/assets/{quadrantDiagram-34T5L4WZ-lMKrSv_t.js → quadrantDiagram-34T5L4WZ-BL2txAAS.js} +1 -1
  52. package/dist/assets/{requirementDiagram-MS252O5E-dWUpHOFb.js → requirementDiagram-MS252O5E-Co3wpBnu.js} +1 -1
  53. package/dist/assets/{sankeyDiagram-XADWPNL6-C8UQx9Bb.js → sankeyDiagram-XADWPNL6-B4KJXdQ4.js} +1 -1
  54. package/dist/assets/{sequenceDiagram-FGHM5R23-CUVNIItJ.js → sequenceDiagram-FGHM5R23-xs5OuzvV.js} +1 -1
  55. package/dist/assets/{stateDiagram-FHFEXIEX-Ct0GamGl.js → stateDiagram-FHFEXIEX-bbEP20JD.js} +1 -1
  56. package/dist/assets/stateDiagram-v2-QKLJ7IA2-XYh9U1A7.js +1 -0
  57. package/dist/assets/{timeline-definition-GMOUNBTQ-ul8Po7f7.js → timeline-definition-GMOUNBTQ-CTc2wVwC.js} +1 -1
  58. package/dist/assets/{vennDiagram-DHZGUBPP-B4AOWQnP.js → vennDiagram-DHZGUBPP-oJpXott7.js} +1 -1
  59. package/dist/assets/{wardley-RL74JXVD-Dr7Wp3AJ.js → wardley-RL74JXVD-aTmOXkKh.js} +1 -1
  60. package/dist/assets/{wardleyDiagram-NUSXRM2D-Ck70faXX.js → wardleyDiagram-NUSXRM2D-C83SOkig.js} +1 -1
  61. package/dist/assets/{xychartDiagram-5P7HB3ND-Bsy5-cNt.js → xychartDiagram-5P7HB3ND-BScws0tF.js} +1 -1
  62. package/dist/index.html +13 -13
  63. package/dist-electron/main.js +153 -116
  64. package/dist-electron/overlay.js +102 -65
  65. package/dist-electron/package.json +1 -0
  66. package/dist-electron/preload.js +8 -8
  67. package/dist-server/bootstrap/crewx-server.js +19 -10
  68. package/dist-server/domain/agent/agent.service.js +12 -0
  69. package/dist-server/domain/agent/dto/update-agent.dto.js +20 -1
  70. package/dist-server/domain/mcp/crewx-tool.factory.js +44 -113
  71. package/dist-server/domain/mcp/mcp.module.js +2 -0
  72. package/dist-server/domain/mcp/mcp.service.js +37 -12
  73. package/dist-server/domain/message/message.service.js +21 -13
  74. package/dist-server/domain/skill/skill.service.js +63 -43
  75. package/dist-server/domain/task/task.module.js +2 -0
  76. package/dist-server/domain/task/task.service.js +17 -10
  77. package/dist-server/domain/thread/dto/update-thread.dto.js +23 -0
  78. package/dist-server/domain/thread/thread.controller.js +16 -0
  79. package/dist-server/domain/thread/thread.service.js +9 -0
  80. package/dist-server/main.js +1 -1
  81. package/dist-server/modules/crewx.module.js +16 -1
  82. package/dist-server/repository/box.repository.js +20 -20
  83. package/dist-server/repository/project.repository.js +13 -13
  84. package/dist-server/repository/request-log.repository.js +10 -10
  85. package/dist-server/repository/task.repository.js +72 -72
  86. package/dist-server/repository/thread.repository.js +78 -58
  87. package/package.json +6 -6
  88. package/packages/cli/dist/bootstrap/crewx-cli.js +12 -0
  89. package/packages/cli/dist/commands/agent.js +23 -23
  90. package/packages/cli/dist/commands/init.js +19 -19
  91. package/packages/cli/dist/commands/parse-common-flags.d.ts +19 -3
  92. package/packages/cli/dist/commands/parse-common-flags.js +46 -6
  93. package/packages/cli/dist/commands/registry.d.ts +13 -0
  94. package/packages/cli/dist/commands/registry.js +29 -0
  95. package/packages/cli/dist/commands/task-db.js +7 -7
  96. package/packages/cli/dist/examples/deny-secrets-plugin.d.ts +22 -0
  97. package/packages/cli/dist/examples/deny-secrets-plugin.js +40 -0
  98. package/packages/cli/dist/main.js +134 -68
  99. package/packages/cli/dist/plugins/examples/echo-hook.d.ts +24 -0
  100. package/packages/cli/dist/plugins/examples/echo-hook.js +60 -0
  101. package/packages/cli/dist/plugins/examples/verify-echo-hook.d.ts +8 -0
  102. package/packages/cli/dist/plugins/examples/verify-echo-hook.js +47 -0
  103. package/packages/cli/dist/plugins/sqlite-tracing.d.ts +11 -0
  104. package/packages/cli/dist/plugins/sqlite-tracing.js +19 -0
  105. package/packages/cli/dist/schema/tasks.d.ts +7 -0
  106. package/packages/cli/dist/schema/tasks.js +48 -0
  107. package/packages/cli/package.json +52 -52
  108. package/scripts/analyze-task-logs.mjs +569 -0
  109. package/scripts/build-manual.mjs +266 -266
  110. package/scripts/emit-dist-server-package-json.mjs +7 -7
  111. package/scripts/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
  112. package/scripts/postinstall.mjs +44 -44
  113. package/scripts/smoke-tarball.mjs +285 -285
  114. package/scripts/snapshot-msg-list.sh +52 -52
  115. package/server.js +167 -164
  116. package/dist/assets/MarketPage-Dwsg6K-B.js +0 -31
  117. package/dist/assets/channel-BP4PNMmz.js +0 -1
  118. package/dist/assets/classDiagram-6PBFFD2Q-Upr3UAcM.js +0 -1
  119. package/dist/assets/classDiagram-v2-HSJHXN6E-Upr3UAcM.js +0 -1
  120. package/dist/assets/clone-B8BP7ReZ.js +0 -1
  121. package/dist/assets/main-CELBpK6r.js +0 -1166
  122. package/dist/assets/main-CmP-VosD.css +0 -10
  123. package/dist/assets/stateDiagram-v2-QKLJ7IA2-CFBLQDEx.js +0 -1
  124. package/dist-server/domain/task/dto/project-usage.dto.js +0 -38
  125. package/dist-server/domain/thread/dto/send-message.dto.js +0 -10
@@ -29,12 +29,12 @@ let ThreadRepository = class ThreadRepository extends base_sqlite_repository_js_
29
29
  ? `WHERE (workspace_id = ? OR workspace_id IS NULL)`
30
30
  : '';
31
31
  const queryParams = workspaceId ? [workspaceId] : [];
32
- const fetched = db.all(`
33
- SELECT id, workspace_id, platform, title, first_message, last_message,
34
- message_count, created_at, updated_at, metadata
35
- FROM threads
36
- ${whereClause}
37
- ORDER BY updated_at DESC
32
+ const fetched = db.all(`
33
+ SELECT id, workspace_id, platform, title, first_message, last_message,
34
+ message_count, created_at, updated_at, metadata
35
+ FROM threads
36
+ ${whereClause}
37
+ ORDER BY updated_at DESC
38
38
  `, queryParams);
39
39
  for (const row of fetched) {
40
40
  if (!seenIds.has(row.id)) {
@@ -66,10 +66,10 @@ let ThreadRepository = class ThreadRepository extends base_sqlite_repository_js_
66
66
  continue;
67
67
  const wsFilter = workspaceId ? ` AND (workspace_id = ? OR workspace_id IS NULL)` : '';
68
68
  const params = workspaceId ? [threadId, workspaceId] : [threadId];
69
- const row = db.get(`
70
- SELECT id, workspace_id, platform, title, first_message, last_message,
71
- message_count, created_at, updated_at, metadata
72
- FROM threads WHERE id = ?${wsFilter}
69
+ const row = db.get(`
70
+ SELECT id, workspace_id, platform, title, first_message, last_message,
71
+ message_count, created_at, updated_at, metadata
72
+ FROM threads WHERE id = ?${wsFilter}
73
73
  `, params);
74
74
  if (row)
75
75
  return row;
@@ -129,22 +129,22 @@ let ThreadRepository = class ThreadRepository extends base_sqlite_repository_js_
129
129
  continue;
130
130
  const wsFilter = workspaceId ? ` AND workspace_id = ?` : '';
131
131
  const statsParams = workspaceId ? [threadId, workspaceId] : [threadId];
132
- const stats = db.get(`
133
- SELECT COUNT(*) as cnt,
134
- COALESCE(SUM(input_tokens), 0) as total_input,
135
- COALESCE(SUM(output_tokens), 0) as total_output,
136
- COALESCE(SUM(cost_usd), 0) as total_cost
137
- FROM tasks
138
- WHERE thread_id = ? AND (parent_task_id IS NULL OR parent_task_id = '')${wsFilter}
132
+ const stats = db.get(`
133
+ SELECT COUNT(*) as cnt,
134
+ COALESCE(SUM(input_tokens), 0) as total_input,
135
+ COALESCE(SUM(output_tokens), 0) as total_output,
136
+ COALESCE(SUM(cost_usd), 0) as total_cost
137
+ FROM tasks
138
+ WHERE thread_id = ? AND (parent_task_id IS NULL OR parent_task_id = '')${wsFilter}
139
139
  `, statsParams);
140
140
  taskCount += stats.cnt;
141
141
  inputTokens += stats.total_input;
142
142
  outputTokens += stats.total_output;
143
143
  costUsd += stats.total_cost;
144
- const agentRows = db.all(`
145
- SELECT DISTINCT agent_id FROM tasks
146
- WHERE thread_id = ? AND agent_id IS NOT NULL AND agent_id != ''
147
- AND (parent_task_id IS NULL OR parent_task_id = '')${wsFilter}
144
+ const agentRows = db.all(`
145
+ SELECT DISTINCT agent_id FROM tasks
146
+ WHERE thread_id = ? AND agent_id IS NOT NULL AND agent_id != ''
147
+ AND (parent_task_id IS NULL OR parent_task_id = '')${wsFilter}
148
148
  `, statsParams);
149
149
  for (const row of agentRows) {
150
150
  agents.add(row.agent_id);
@@ -175,13 +175,13 @@ let ThreadRepository = class ThreadRepository extends base_sqlite_repository_js_
175
175
  continue;
176
176
  const wsFilter = workspaceId ? ` AND workspace_id = ?` : '';
177
177
  const taskParams = workspaceId ? [threadId, workspaceId] : [threadId];
178
- const tasks = db.all(`
179
- SELECT id, agent_id, prompt, result, status, started_at, completed_at,
180
- duration_ms, thread_id, logs, input_tokens, output_tokens, cost_usd,
181
- error, parent_task_id, caller_agent_id
182
- FROM tasks
183
- WHERE thread_id = ? AND (parent_task_id IS NULL OR parent_task_id = '')${wsFilter}
184
- ORDER BY started_at ASC
178
+ const tasks = db.all(`
179
+ SELECT id, agent_id, prompt, result, status, started_at, completed_at,
180
+ duration_ms, thread_id, logs, input_tokens, output_tokens, cost_usd,
181
+ error, parent_task_id, caller_agent_id
182
+ FROM tasks
183
+ WHERE thread_id = ? AND (parent_task_id IS NULL OR parent_task_id = '')${wsFilter}
184
+ ORDER BY started_at ASC
185
185
  `, taskParams);
186
186
  for (const t of tasks) {
187
187
  if (!seenIds.has(t.id)) {
@@ -215,12 +215,12 @@ let ThreadRepository = class ThreadRepository extends base_sqlite_repository_js_
215
215
  continue;
216
216
  const wsFilter = workspaceId ? ` AND workspace_id = ?` : '';
217
217
  const taskParams = workspaceId ? [threadId, workspaceId] : [threadId];
218
- const tasks = db.all(`
219
- SELECT id, agent_id, prompt, result, status, started_at, completed_at,
220
- duration_ms, thread_id, logs, input_tokens, output_tokens, cost_usd,
221
- error, parent_task_id, caller_agent_id
222
- FROM tasks WHERE thread_id = ?${wsFilter}
223
- ORDER BY started_at ASC
218
+ const tasks = db.all(`
219
+ SELECT id, agent_id, prompt, result, status, started_at, completed_at,
220
+ duration_ms, thread_id, logs, input_tokens, output_tokens, cost_usd,
221
+ error, parent_task_id, caller_agent_id
222
+ FROM tasks WHERE thread_id = ?${wsFilter}
223
+ ORDER BY started_at ASC
224
224
  `, taskParams);
225
225
  for (const t of tasks) {
226
226
  if (!seenIds.has(t.id)) {
@@ -250,17 +250,17 @@ let ThreadRepository = class ThreadRepository extends base_sqlite_repository_js_
250
250
  try {
251
251
  const wsFilter = workspaceId ? ` AND workspace_id = ?` : '';
252
252
  const findParams = workspaceId ? [taskId, threadId, workspaceId] : [taskId, threadId];
253
- const row = db.get(`
254
- SELECT id, agent_id, prompt, result, status, started_at, completed_at,
255
- duration_ms, thread_id, logs, input_tokens, output_tokens, cost_usd,
256
- error, parent_task_id, caller_agent_id
257
- FROM tasks WHERE id = ? AND thread_id = ?${wsFilter}
253
+ const row = db.get(`
254
+ SELECT id, agent_id, prompt, result, status, started_at, completed_at,
255
+ duration_ms, thread_id, logs, input_tokens, output_tokens, cost_usd,
256
+ error, parent_task_id, caller_agent_id
257
+ FROM tasks WHERE id = ? AND thread_id = ?${wsFilter}
258
258
  `, findParams);
259
259
  if (!row)
260
260
  continue;
261
- const children = db.all(`SELECT id, agent_id, prompt, result, status, started_at, completed_at,
262
- duration_ms, thread_id, logs, input_tokens, output_tokens, cost_usd,
263
- error, parent_task_id, caller_agent_id
261
+ const children = db.all(`SELECT id, agent_id, prompt, result, status, started_at, completed_at,
262
+ duration_ms, thread_id, logs, input_tokens, output_tokens, cost_usd,
263
+ error, parent_task_id, caller_agent_id
264
264
  FROM tasks WHERE parent_task_id = ? ORDER BY started_at ASC`, [row.id]);
265
265
  return { task: row, children };
266
266
  }
@@ -287,13 +287,13 @@ let ThreadRepository = class ThreadRepository extends base_sqlite_repository_js_
287
287
  continue;
288
288
  const db = this.openReadonly(dbPath);
289
289
  try {
290
- const rows = db.all(`
291
- SELECT id, agent_id, status, started_at, completed_at,
292
- duration_ms, thread_id, input_tokens, output_tokens, cost_usd, error
293
- FROM tasks
294
- WHERE thread_id IN (${placeholders})
295
- AND (parent_task_id IS NULL OR parent_task_id = '')
296
- ORDER BY started_at ASC
290
+ const rows = db.all(`
291
+ SELECT id, agent_id, status, started_at, completed_at,
292
+ duration_ms, thread_id, input_tokens, output_tokens, cost_usd, error
293
+ FROM tasks
294
+ WHERE thread_id IN (${placeholders})
295
+ AND (parent_task_id IS NULL OR parent_task_id = '')
296
+ ORDER BY started_at ASC
297
297
  `, threadIds);
298
298
  for (const row of rows) {
299
299
  const tid = row.thread_id;
@@ -313,6 +313,26 @@ let ThreadRepository = class ThreadRepository extends base_sqlite_repository_js_
313
313
  }
314
314
  return result;
315
315
  }
316
+ /** Update thread title and set title_locked=1 to prevent auto-overwrite.
317
+ * Returns true if the thread was found and updated, false if not found. */
318
+ updateThreadTitle(threadId, title, workspaceId) {
319
+ const dbPath = this.resolveDbPath();
320
+ if (!(0, fs_1.existsSync)(dbPath))
321
+ return false;
322
+ const db = this.openReadWrite(dbPath);
323
+ try {
324
+ const wsFilter = workspaceId ? ` AND (workspace_id = ? OR workspace_id IS NULL)` : '';
325
+ const params = workspaceId ? [threadId, workspaceId] : [threadId];
326
+ const existing = db.get(`SELECT id FROM threads WHERE id = ?${wsFilter}`, params);
327
+ if (!existing)
328
+ return false;
329
+ db.run(`UPDATE threads SET title = ?, title_locked = 1, updated_at = datetime('now') WHERE id = ?`, [title, threadId]);
330
+ return true;
331
+ }
332
+ finally {
333
+ db.close();
334
+ }
335
+ }
316
336
  /** UPSERT thread (used by MSG.SEND).
317
337
  * workspace_id is required on INSERT to prevent NULL workspace rows. */
318
338
  upsertThread(threadId, opts) {
@@ -323,18 +343,18 @@ let ThreadRepository = class ThreadRepository extends base_sqlite_repository_js_
323
343
  try {
324
344
  const existing = db.get('SELECT id, message_count FROM threads WHERE id = ?', [threadId]);
325
345
  if (existing) {
326
- db.run(`
327
- UPDATE threads
328
- SET updated_at = datetime('now'), message_count = message_count + 1
329
- WHERE id = ?
346
+ db.run(`
347
+ UPDATE threads
348
+ SET updated_at = datetime('now'), message_count = message_count + 1
349
+ WHERE id = ?
330
350
  `, [threadId]);
331
351
  }
332
352
  else {
333
- db.run(`
334
- INSERT OR REPLACE INTO threads
335
- (id, workspace_id, platform, title, first_message, message_count, created_at, updated_at)
336
- VALUES (?, ?, 'cli', ?, ?, 1, datetime('now'), datetime('now'))
337
- `, [threadId, opts.workspaceId ?? null, opts.title, opts.firstMessage]);
353
+ db.run(`
354
+ INSERT OR REPLACE INTO threads
355
+ (id, workspace_id, platform, title, first_message, message_count, created_at, updated_at)
356
+ VALUES (?, ?, ?, ?, ?, 1, datetime('now'), datetime('now'))
357
+ `, [threadId, opts.workspaceId ?? null, opts.platform ?? 'cli', opts.title, opts.firstMessage]);
338
358
  }
339
359
  }
340
360
  finally {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "crewx",
3
- "version": "0.8.1",
3
+ "version": "0.8.2-rc.2",
4
4
  "description": "CrewX — AI agent team dashboard with Electron UI and CLI (Web + Electron + Global CLI)",
5
5
  "main": "server.js",
6
6
  "bin": {
@@ -97,17 +97,17 @@
97
97
  "yargs": "^17.7.0",
98
98
  "zod": "^3.22.0",
99
99
  "zustand": "^4.5.0",
100
+ "@crewx/cli": "0.8.2-rc.2",
100
101
  "@crewx/cron": "0.1.7",
101
- "@crewx/cli": "0.8.1",
102
102
  "@crewx/doc": "0.1.7",
103
103
  "@crewx/knowledge-core": "0.1.5",
104
104
  "@crewx/memory": "0.1.9",
105
+ "@crewx/sdk": "0.8.2-rc.2",
105
106
  "@crewx/search": "0.1.8",
106
107
  "@crewx/shared": "0.0.4",
107
- "@crewx/workflow": "0.3.8",
108
- "@crewx/sdk": "0.8.1",
108
+ "@crewx/skill": "0.1.7",
109
109
  "@crewx/wbs": "0.1.8",
110
- "@crewx/skill": "0.1.7"
110
+ "@crewx/workflow": "0.3.8"
111
111
  },
112
112
  "devDependencies": {
113
113
  "@nestjs/cli": "^11.0.16",
@@ -145,7 +145,7 @@
145
145
  "typescript": "^5.3.3",
146
146
  "vite": "^5.0.10",
147
147
  "vite-plugin-electron": "^0.29.0",
148
- "vitest": "^4.0.18"
148
+ "vitest": "^3.2.4"
149
149
  },
150
150
  "optionalDependencies": {
151
151
  "electron": "^39.2.7"
@@ -1,12 +1,18 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
3
6
  exports.createCliCrewx = createCliCrewx;
4
7
  const fs_1 = require("fs");
5
8
  const path_1 = require("path");
9
+ const os_1 = require("os");
10
+ const better_sqlite3_1 = __importDefault(require("better-sqlite3"));
6
11
  const sdk_1 = require("@crewx/sdk");
7
12
  const plugins_1 = require("@crewx/sdk/plugins");
8
13
  const register_builtin_tools_1 = require("../register-builtin-tools");
9
14
  const version_1 = require("../utils/version");
15
+ const tasks_1 = require("../schema/tasks");
10
16
  /**
11
17
  * Build a Crewx instance with CLI-standard plugins (FileLogger + SqliteTracing)
12
18
  * and built-in tools registered. Use this from any CLI command that needs a
@@ -39,6 +45,12 @@ async function createCliCrewx(configPath = process.env.CREWX_CONFIG ?? 'crewx.ya
39
45
  // No explicit config and file doesn't exist — built-in-only mode
40
46
  yamlPath = undefined;
41
47
  }
48
+ // Ensure ~/.crewx/crewx.db schema exists before SqliteTracingPlugin opens it.
49
+ const dbDir = (0, path_1.join)((0, os_1.homedir)(), '.crewx');
50
+ (0, fs_1.mkdirSync)(dbDir, { recursive: true });
51
+ const schemaDb = new better_sqlite3_1.default((0, path_1.join)(dbDir, 'crewx.db'));
52
+ (0, tasks_1.ensureTasksSchema)(schemaDb);
53
+ schemaDb.close();
42
54
  // Pass ourselves back to the SDK: when this Crewx encounters a file:// remote
43
55
  // agent, the SDK uses this factory to bootstrap the target Crewx instance
44
56
  // with the same plugin set (FileLogger + SqliteTracing + built-in tools).
@@ -219,28 +219,28 @@ function loadAgentSkills() {
219
219
  }
220
220
  }
221
221
  function printAgentHelp() {
222
- console.log(`
223
- CrewX Agent Management
224
-
225
- Usage:
226
- crewx agent # List configured agents (default)
227
- crewx agent ls # List configured agents
228
- crewx agent list # Alias for ls
229
- crewx agent prompt <id> # Inspect rendered agent prompt
230
-
231
- Filter Options (for agent ls):
232
- --role <value> Filter by agent role (comma-separated for multiple: PM,Dev)
233
- --team <value> Filter by agent team (comma-separated for multiple)
234
- --provider <value> Filter by provider (comma-separated, partial match: claude)
235
-
236
- Examples:
237
- crewx agent
238
- crewx agent ls
239
- crewx agent ls --role=PM
240
- crewx agent ls --team="CrewX Core 개발팀"
241
- crewx agent ls --provider=claude
242
- crewx agent ls --role=PM --provider=claude
243
- crewx agent ls --role=PM,general
244
- CREWX_CONFIG=./crewx.yaml crewx agent list
222
+ console.log(`
223
+ CrewX Agent Management
224
+
225
+ Usage:
226
+ crewx agent # List configured agents (default)
227
+ crewx agent ls # List configured agents
228
+ crewx agent list # Alias for ls
229
+ crewx agent prompt <id> # Inspect rendered agent prompt
230
+
231
+ Filter Options (for agent ls):
232
+ --role <value> Filter by agent role (comma-separated for multiple: PM,Dev)
233
+ --team <value> Filter by agent team (comma-separated for multiple)
234
+ --provider <value> Filter by provider (comma-separated, partial match: claude)
235
+
236
+ Examples:
237
+ crewx agent
238
+ crewx agent ls
239
+ crewx agent ls --role=PM
240
+ crewx agent ls --team="CrewX Core 개발팀"
241
+ crewx agent ls --provider=claude
242
+ crewx agent ls --role=PM --provider=claude
243
+ crewx agent ls --role=PM,general
244
+ CREWX_CONFIG=./crewx.yaml crewx agent list
245
245
  `.trim());
246
246
  }
@@ -56,27 +56,27 @@ function escapeYamlBlock(text) {
56
56
  }
57
57
  function generateDefaultYaml(agents) {
58
58
  const agentBlocks = agents
59
- .map((a) => ` - id: "${a.id}"
60
- name: "${a.name}"
61
- role: "${a.role}"
62
- team: "${a.team}"
63
- provider: "${a.provider}"
64
- default_model: "${a.default_model}"
65
- working_directory: "${a.working_directory}"
66
- description: "${a.description}"
67
- system_prompt: |
59
+ .map((a) => ` - id: "${a.id}"
60
+ name: "${a.name}"
61
+ role: "${a.role}"
62
+ team: "${a.team}"
63
+ provider: "${a.provider}"
64
+ default_model: "${a.default_model}"
65
+ working_directory: "${a.working_directory}"
66
+ description: "${a.description}"
67
+ system_prompt: |
68
68
  ${escapeYamlBlock(a.system_prompt)}`)
69
69
  .join('\n\n');
70
- return `# CrewX Agents Configuration
71
- # Generated by 'crewx init'
72
-
73
- agents:
74
- ${agentBlocks}
75
-
76
- # Usage examples:
77
- # crewx query "@planner analyze this codebase"
78
- # crewx execute "@developer implement the feature described above"
79
- # crewx agent ls
70
+ return `# CrewX Agents Configuration
71
+ # Generated by 'crewx init'
72
+
73
+ agents:
74
+ ${agentBlocks}
75
+
76
+ # Usage examples:
77
+ # crewx query "@planner analyze this codebase"
78
+ # crewx execute "@developer implement the feature described above"
79
+ # crewx agent ls
80
80
  `;
81
81
  }
82
82
  /**
@@ -3,7 +3,11 @@
3
3
  *
4
4
  * Supports both `--flag=value` and `--flag value` forms.
5
5
  * Handles: --thread, --provider, --metadata, --verbose, --config/-c,
6
- * --output-format, --effort.
6
+ * --output-format, --effort, --prompt-file/-f.
7
+ *
8
+ * Strict mode: unknown --xxx tokens after known flags are consumed throw an
9
+ * error instead of silently leaking into the message prompt.
10
+ * Use `--` sentinel to pass literal flag-like tokens as positional args.
7
11
  */
8
12
  export interface CommonFlags {
9
13
  /** Thread name for conversation continuity. */
@@ -22,12 +26,24 @@ export interface CommonFlags {
22
26
  effort?: string;
23
27
  /** Path to a file whose content is used as the prompt body (-f/--prompt-file). */
24
28
  promptFile?: string;
25
- /** Remaining non-flag arguments. */
29
+ /** Remaining non-flag positional arguments. */
26
30
  rest: string[];
27
31
  }
32
+ /**
33
+ * Thrown by parseCommonFlags when an unrecognized --xxx flag is encountered.
34
+ * Callers can distinguish this from other errors to map to exit code 2.
35
+ */
36
+ export declare class UnknownOptionError extends Error {
37
+ constructor(message: string);
38
+ }
28
39
  /**
29
40
  * Parse common CLI flags from an args array.
30
- * Returns parsed flags and remaining positional arguments.
41
+ *
42
+ * Strict mode: any unconsumed `--xxx` token (outside a `--` escape block)
43
+ * throws `UnknownOptionError: Unknown option: --xxx.`
44
+ *
45
+ * Tokens after `--` are treated as positional and included in `rest` as-is.
46
+ * The `--` sentinel itself is NOT included in `rest`.
31
47
  */
32
48
  export declare function parseCommonFlags(args: string[]): CommonFlags;
33
49
  /**
@@ -4,9 +4,14 @@
4
4
  *
5
5
  * Supports both `--flag=value` and `--flag value` forms.
6
6
  * Handles: --thread, --provider, --metadata, --verbose, --config/-c,
7
- * --output-format, --effort.
7
+ * --output-format, --effort, --prompt-file/-f.
8
+ *
9
+ * Strict mode: unknown --xxx tokens after known flags are consumed throw an
10
+ * error instead of silently leaking into the message prompt.
11
+ * Use `--` sentinel to pass literal flag-like tokens as positional args.
8
12
  */
9
13
  Object.defineProperty(exports, "__esModule", { value: true });
14
+ exports.UnknownOptionError = void 0;
10
15
  exports.parseCommonFlags = parseCommonFlags;
11
16
  exports.parseMetadata = parseMetadata;
12
17
  /**
@@ -35,9 +40,25 @@ function parseFlag(args, ...names) {
35
40
  function hasFlag(args, ...names) {
36
41
  return names.some(name => args.includes(name));
37
42
  }
43
+ /**
44
+ * Thrown by parseCommonFlags when an unrecognized --xxx flag is encountered.
45
+ * Callers can distinguish this from other errors to map to exit code 2.
46
+ */
47
+ class UnknownOptionError extends Error {
48
+ constructor(message) {
49
+ super(message);
50
+ this.name = 'UnknownOptionError';
51
+ }
52
+ }
53
+ exports.UnknownOptionError = UnknownOptionError;
38
54
  /**
39
55
  * Parse common CLI flags from an args array.
40
- * Returns parsed flags and remaining positional arguments.
56
+ *
57
+ * Strict mode: any unconsumed `--xxx` token (outside a `--` escape block)
58
+ * throws `UnknownOptionError: Unknown option: --xxx.`
59
+ *
60
+ * Tokens after `--` are treated as positional and included in `rest` as-is.
61
+ * The `--` sentinel itself is NOT included in `rest`.
41
62
  */
42
63
  function parseCommonFlags(args) {
43
64
  const thread = parseFlag(args, '--thread');
@@ -48,7 +69,7 @@ function parseCommonFlags(args) {
48
69
  const effort = parseFlag(args, '--effort');
49
70
  const promptFile = parseFlag(args, '--prompt-file', '-f');
50
71
  const verbose = hasFlag(args, '--verbose');
51
- // Collect the set of consumed positions
72
+ // Collect consumed positions for known flags
52
73
  const consumed = new Set();
53
74
  const flagPairs = [
54
75
  { names: ['--thread'] },
@@ -61,12 +82,10 @@ function parseCommonFlags(args) {
61
82
  ];
62
83
  for (let i = 0; i < args.length; i++) {
63
84
  const arg = args[i];
64
- // Boolean flags
65
85
  if (arg === '--verbose') {
66
86
  consumed.add(i);
67
87
  continue;
68
88
  }
69
- // Value flags
70
89
  for (const { names } of flagPairs) {
71
90
  for (const name of names) {
72
91
  if (arg.startsWith(`${name}=`)) {
@@ -80,7 +99,28 @@ function parseCommonFlags(args) {
80
99
  }
81
100
  }
82
101
  }
83
- const rest = args.filter((_, i) => !consumed.has(i));
102
+ // Build rest with strict unknown-flag detection and -- escape support
103
+ const rest = [];
104
+ let escapeMode = false;
105
+ for (let i = 0; i < args.length; i++) {
106
+ if (consumed.has(i))
107
+ continue;
108
+ const token = args[i];
109
+ if (!escapeMode && token === '--') {
110
+ escapeMode = true; // consume -- sentinel; everything after is positional
111
+ continue;
112
+ }
113
+ if (!escapeMode && token.startsWith('--')) {
114
+ throw new UnknownOptionError(`Unknown option: ${token}. Use "--" to pass literal flag-like tokens as message.\n` +
115
+ ` Did you mean: crewx serve ${token}\n` +
116
+ ` or: crewx q -- ${token} <message> (to include in message)`);
117
+ }
118
+ // Reject short -x flags that are not numeric (e.g. -f was consumed above)
119
+ if (!escapeMode && token.startsWith('-') && token.length > 1 && !/^-?\d/.test(token)) {
120
+ throw new UnknownOptionError(`Unknown option: ${token}`);
121
+ }
122
+ rest.push(token);
123
+ }
84
124
  return { thread, provider, metadata, verbose, config, outputFormat, effort, promptFile, rest };
85
125
  }
86
126
  /**
@@ -0,0 +1,13 @@
1
+ /**
2
+ * CLI command registry — SSOT for commands handled by the CLI engine.
3
+ *
4
+ * Consumed by:
5
+ * - main.ts dispatch (to avoid switch/case literal drift)
6
+ * - bin/__tests__/crewx-entrypoint.test.ts (SSOT assertion vs CLI_SUBCOMMANDS)
7
+ */
8
+ /** Core commands handled by the main.ts switch/case or hook routing. */
9
+ export declare const KNOWN_COMMANDS: Set<string>;
10
+ /** Built-in tool commands routed via handleBuiltin(). */
11
+ export declare const BUILTIN_COMMAND_NAMES: Set<string>;
12
+ /** Commands not yet migrated from cli-bak — show a migration message. */
13
+ export declare const NOT_YET_MIGRATED: Set<string>;
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+ /**
3
+ * CLI command registry — SSOT for commands handled by the CLI engine.
4
+ *
5
+ * Consumed by:
6
+ * - main.ts dispatch (to avoid switch/case literal drift)
7
+ * - bin/__tests__/crewx-entrypoint.test.ts (SSOT assertion vs CLI_SUBCOMMANDS)
8
+ */
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.NOT_YET_MIGRATED = exports.BUILTIN_COMMAND_NAMES = exports.KNOWN_COMMANDS = void 0;
11
+ /** Core commands handled by the main.ts switch/case or hook routing. */
12
+ exports.KNOWN_COMMANDS = new Set([
13
+ 'q', 'query',
14
+ 'x', 'execute',
15
+ 'agent',
16
+ 'ps', 'kill', 'result', 'log',
17
+ 'doctor', 'init',
18
+ 'help',
19
+ 'slack', 'slack:files',
20
+ 'hook', 'hook-dispatch',
21
+ ]);
22
+ /** Built-in tool commands routed via handleBuiltin(). */
23
+ exports.BUILTIN_COMMAND_NAMES = new Set([
24
+ 'memory', 'search', 'doc', 'wbs', 'cron', 'workflow', 'skill',
25
+ ]);
26
+ /** Commands not yet migrated from cli-bak — show a migration message. */
27
+ exports.NOT_YET_MIGRATED = new Set([
28
+ 'template', 'templates', 'chat', 'mcp',
29
+ ]);
@@ -33,8 +33,8 @@ function getRunningTasks(dbRoot) {
33
33
  if (!db)
34
34
  return [];
35
35
  try {
36
- return db.prepare(`SELECT id, agent_id, prompt, mode, status, pid, started_at, completed_at,
37
- result, error, duration_ms
36
+ return db.prepare(`SELECT id, agent_id, prompt, mode, status, pid, started_at, completed_at,
37
+ result, error, duration_ms
38
38
  FROM tasks WHERE status = 'running' ORDER BY started_at DESC`).all();
39
39
  }
40
40
  finally {
@@ -47,8 +47,8 @@ function getAllTasks(dbRoot) {
47
47
  if (!db)
48
48
  return [];
49
49
  try {
50
- return db.prepare(`SELECT id, agent_id, prompt, mode, status, pid, started_at, completed_at,
51
- result, error, duration_ms
50
+ return db.prepare(`SELECT id, agent_id, prompt, mode, status, pid, started_at, completed_at,
51
+ result, error, duration_ms
52
52
  FROM tasks ORDER BY started_at DESC`).all();
53
53
  }
54
54
  finally {
@@ -61,8 +61,8 @@ function getTask(id, dbRoot) {
61
61
  if (!db)
62
62
  return undefined;
63
63
  try {
64
- return db.prepare(`SELECT id, agent_id, prompt, mode, status, pid, started_at, completed_at,
65
- result, error, duration_ms
64
+ return db.prepare(`SELECT id, agent_id, prompt, mode, status, pid, started_at, completed_at,
65
+ result, error, duration_ms
66
66
  FROM tasks WHERE id = ?`).get(id);
67
67
  }
68
68
  finally {
@@ -97,7 +97,7 @@ function killTask(id, dbRoot) {
97
97
  // ESRCH = process already gone — still clean up the record
98
98
  }
99
99
  }
100
- db.prepare(`UPDATE tasks SET status='failed', error='killed by user',
100
+ db.prepare(`UPDATE tasks SET status='failed', error='killed by user',
101
101
  completed_at=?, pid=NULL WHERE id=?`).run(new Date().toISOString(), id);
102
102
  return { ok: true, message: `Killed task ${id}${task.pid ? ` (PID: ${task.pid})` : ''}` };
103
103
  }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * DenyIfTouchesSecretsPlugin — Phase 0 demo HookPlugin.
3
+ *
4
+ * Denies Bash tool calls whose command string contains ".env".
5
+ * Pure string matching — trivially bypassable (see README).
6
+ *
7
+ * SECURITY NOTE:
8
+ * Do NOT include tool.input content in deny reasons.
9
+ * Use static messages only to prevent prompt injection.
10
+ * ✅ return ctx.deny('Secrets-related command');
11
+ * ❌ return ctx.deny(`Blocked: ${cmd}`);
12
+ */
13
+ import { HookPlugin } from '@crewx/sdk/hooks';
14
+ import type { HookContext, HookResult } from '@crewx/sdk/hooks';
15
+ export declare class DenyIfTouchesSecretsPlugin extends HookPlugin {
16
+ readonly name = "deny-secrets";
17
+ readonly version = "0.1.0";
18
+ readonly capabilities: {
19
+ required: readonly ["deny"];
20
+ };
21
+ run(ctx: HookContext): Promise<HookResult>;
22
+ }
@@ -0,0 +1,40 @@
1
+ "use strict";
2
+ /**
3
+ * DenyIfTouchesSecretsPlugin — Phase 0 demo HookPlugin.
4
+ *
5
+ * Denies Bash tool calls whose command string contains ".env".
6
+ * Pure string matching — trivially bypassable (see README).
7
+ *
8
+ * SECURITY NOTE:
9
+ * Do NOT include tool.input content in deny reasons.
10
+ * Use static messages only to prevent prompt injection.
11
+ * ✅ return ctx.deny('Secrets-related command');
12
+ * ❌ return ctx.deny(`Blocked: ${cmd}`);
13
+ */
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.DenyIfTouchesSecretsPlugin = void 0;
16
+ const hooks_1 = require("@crewx/sdk/hooks");
17
+ const SECRET_PATTERNS = ['.env', 'credentials.json', 'service-account-key.json'];
18
+ const SHELL_TOOL_NAMES = new Set(['Bash', 'shell', 'local_shell']);
19
+ class DenyIfTouchesSecretsPlugin extends hooks_1.HookPlugin {
20
+ name = 'deny-secrets';
21
+ version = '0.1.0';
22
+ capabilities = { required: ['deny'] };
23
+ async run(ctx) {
24
+ if (!SHELL_TOOL_NAMES.has(ctx.tool.rawName) && ctx.tool.name !== 'shell') {
25
+ return ctx.pass();
26
+ }
27
+ const input = ctx.tool.input;
28
+ const command = input?.command;
29
+ if (typeof command !== 'string') {
30
+ return ctx.pass();
31
+ }
32
+ for (const pattern of SECRET_PATTERNS) {
33
+ if (command.includes(pattern)) {
34
+ return ctx.deny('Secrets-related command');
35
+ }
36
+ }
37
+ return ctx.pass();
38
+ }
39
+ }
40
+ exports.DenyIfTouchesSecretsPlugin = DenyIfTouchesSecretsPlugin;