profclaw 0.0.1-beta.2 → 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
@@ -1,694 +0,0 @@
1
- /**
2
- * GLINR Operations Tools
3
- *
4
- * Ticket, project, and sprint management through AI chat.
5
- * These tools allow the AI to create and manage GLINR's own data.
6
- */
7
- import { z } from 'zod';
8
- import { randomUUID } from 'node:crypto';
9
- import { getDb } from '../../../storage/index.js';
10
- import { projects, tickets } from '../../../storage/schema.js';
11
- import { eq, sql, desc, and, like, or } from 'drizzle-orm';
12
- import { logger } from '../../../utils/logger.js';
13
- // =============================================================================
14
- // Availability Check
15
- // =============================================================================
16
- /** Cache database availability to avoid repeated checks */
17
- let dbAvailabilityCache = null;
18
- let lastDbCheck = 0;
19
- const DB_CHECK_INTERVAL_MS = 30000; // Recheck every 30 seconds
20
- /**
21
- * Check if database is available
22
- * Rechecks periodically in case database becomes available after startup
23
- */
24
- function checkDatabaseAvailability() {
25
- const now = Date.now();
26
- // Use cache if recent and was available
27
- if (dbAvailabilityCache && dbAvailabilityCache.available && now - lastDbCheck < DB_CHECK_INTERVAL_MS) {
28
- return dbAvailabilityCache;
29
- }
30
- try {
31
- // This will throw if storage is not initialized
32
- getDb();
33
- dbAvailabilityCache = { available: true };
34
- lastDbCheck = now;
35
- }
36
- catch (error) {
37
- dbAvailabilityCache = {
38
- available: false,
39
- reason: 'Database not initialized',
40
- };
41
- lastDbCheck = now;
42
- }
43
- return dbAvailabilityCache;
44
- }
45
- // =============================================================================
46
- // Schemas
47
- // =============================================================================
48
- const CreateTicketParamsSchema = z.object({
49
- projectKey: z.string().describe('Project key (e.g., "GLINR")'),
50
- title: z.string().min(1).describe('Ticket title'),
51
- description: z.string().optional().describe('Detailed description'),
52
- type: z.enum(['task', 'bug', 'story', 'epic', 'subtask', 'feature', 'improvement']).default('task'),
53
- priority: z.enum(['critical', 'high', 'medium', 'low', 'none']).default('medium'),
54
- labels: z.array(z.string()).optional().describe('Labels/tags'),
55
- storyPoints: z.number().optional().describe('Story points estimate'),
56
- });
57
- const CreateProjectParamsSchema = z.object({
58
- name: z.string().min(1).describe('Project name'),
59
- key: z.string().min(2).max(10).describe('Project key prefix (e.g., "GLINR")'),
60
- description: z.string().optional().describe('Project description'),
61
- icon: z.string().optional().describe('Emoji icon (e.g., "🚀")'),
62
- color: z.string().optional().describe('Hex color (e.g., "#6366f1")'),
63
- });
64
- const ListTicketsParamsSchema = z.object({
65
- projectKey: z.string().optional().describe('Filter by project'),
66
- status: z.string().optional().describe('Filter by status'),
67
- type: z.string().optional().describe('Filter by type'),
68
- priority: z.string().optional().describe('Filter by priority'),
69
- search: z.string().optional().describe('Search in title/description'),
70
- limit: z.number().default(20).describe('Max results'),
71
- });
72
- const ListProjectsParamsSchema = z.object({
73
- status: z.enum(['active', 'archived', 'all']).default('active'),
74
- });
75
- const UpdateTicketParamsSchema = z.object({
76
- ticketKey: z.string().describe('Ticket key (e.g., "GLINR-123")'),
77
- title: z.string().optional().describe('New title'),
78
- description: z.string().optional().describe('New description'),
79
- status: z.enum(['backlog', 'todo', 'in_progress', 'in_review', 'done', 'cancelled']).optional(),
80
- priority: z.enum(['critical', 'high', 'medium', 'low', 'none']).optional(),
81
- type: z.enum(['task', 'bug', 'story', 'epic', 'subtask', 'feature', 'improvement']).optional(),
82
- labels: z.array(z.string()).optional(),
83
- storyPoints: z.number().optional(),
84
- });
85
- const GetTicketParamsSchema = z.object({
86
- ticketKey: z.string().describe('Ticket key (e.g., "GLINR-123")'),
87
- });
88
- const AddCommentParamsSchema = z.object({
89
- ticketKey: z.string().describe('Ticket key (e.g., "GLINR-123")'),
90
- content: z.string().min(1).describe('Comment content (supports markdown)'),
91
- });
92
- const MoveTicketParamsSchema = z.object({
93
- ticketKey: z.string().describe('Ticket key (e.g., "GLINR-123")'),
94
- targetProjectKey: z.string().describe('Target project key (e.g., "MOBILE")'),
95
- });
96
- const AssignTicketParamsSchema = z.object({
97
- ticketKey: z.string().describe('Ticket key (e.g., "GLINR-123")'),
98
- assignee: z.string().optional().describe('Human assignee name or email'),
99
- assigneeAgent: z.enum(['claude', 'openclaw', 'ollama', 'gemini']).optional().describe('AI agent to assign'),
100
- unassign: z.boolean().optional().describe('Set to true to remove assignment'),
101
- });
102
- // =============================================================================
103
- // Helper Functions
104
- // =============================================================================
105
- async function getProjectByKey(key) {
106
- const client = getDb();
107
- const result = await client.select().from(projects).where(eq(projects.key, key.toUpperCase())).limit(1);
108
- return result[0] || null;
109
- }
110
- async function getNextTicketSequence(projectId) {
111
- const client = getDb();
112
- const result = await client
113
- .select({ maxNum: sql `COALESCE(MAX(${tickets.sequence}), 0)` })
114
- .from(tickets)
115
- .where(eq(tickets.projectId, projectId));
116
- return (result[0]?.maxNum || 0) + 1;
117
- }
118
- async function getTicketByKey(ticketKey) {
119
- const client = getDb();
120
- const [projectKey, numStr] = ticketKey.split('-');
121
- const sequence = parseInt(numStr, 10);
122
- if (!projectKey || isNaN(sequence)) {
123
- return null;
124
- }
125
- const project = await getProjectByKey(projectKey);
126
- if (!project)
127
- return null;
128
- const result = await client
129
- .select()
130
- .from(tickets)
131
- .where(and(eq(tickets.projectId, project.id), eq(tickets.sequence, sequence)))
132
- .limit(1);
133
- return result[0] ? { ...result[0], projectKey: project.key } : null;
134
- }
135
- /**
136
- * Safely format a date value to ISO string
137
- * Handles Date objects, timestamps, and invalid values
138
- */
139
- function formatDateSafe(value) {
140
- if (!value)
141
- return new Date().toISOString();
142
- // If it's already a valid Date
143
- if (value instanceof Date && !isNaN(value.getTime())) {
144
- return value.toISOString();
145
- }
146
- // If it's a number (Unix timestamp in seconds or ms)
147
- if (typeof value === 'number') {
148
- // Detect if it's seconds or milliseconds
149
- const ms = value > 1e12 ? value : value * 1000;
150
- const date = new Date(ms);
151
- if (!isNaN(date.getTime())) {
152
- return date.toISOString();
153
- }
154
- }
155
- // If it's a string, try to parse it
156
- if (typeof value === 'string') {
157
- const date = new Date(value);
158
- if (!isNaN(date.getTime())) {
159
- return date.toISOString();
160
- }
161
- }
162
- // Fallback to now
163
- return new Date().toISOString();
164
- }
165
- // =============================================================================
166
- // Create Ticket Tool
167
- // =============================================================================
168
- export const createTicketTool = {
169
- name: 'create_ticket',
170
- description: `Create a new ticket/task/bug in GLINR. Use AFTER list_projects to get a valid projectKey.
171
-
172
- IMPORTANT: You MUST call this tool after list_projects when the user asks to create a ticket/task/bug.
173
- Required params: projectKey (from list_projects), title (from user request)
174
- Optional: type (bug/feature/task), priority, description
175
-
176
- The response includes a 'url' field - use this to create a clickable markdown link in your summary:
177
- Example: "Created ticket [GLINR-42](/tickets/abc123)"
178
-
179
- DO NOT just list projects and stop - you must CREATE the ticket to complete the user's request.`,
180
- category: 'glinr',
181
- securityLevel: 'safe',
182
- allowedHosts: ['sandbox', 'gateway', 'local'],
183
- parameters: CreateTicketParamsSchema,
184
- isAvailable: checkDatabaseAvailability,
185
- examples: [
186
- {
187
- description: 'Create a bug ticket',
188
- params: { projectKey: 'GLINR', title: 'Fix login button', type: 'bug', priority: 'high' },
189
- },
190
- {
191
- description: 'Create a feature with description',
192
- params: {
193
- projectKey: 'GLINR',
194
- title: 'Add dark mode',
195
- description: 'Implement dark mode toggle in settings',
196
- type: 'feature',
197
- },
198
- },
199
- ],
200
- async execute(_context, params) {
201
- try {
202
- const client = getDb();
203
- // Get project
204
- const project = await getProjectByKey(params.projectKey);
205
- if (!project) {
206
- return {
207
- success: false,
208
- error: {
209
- code: 'PROJECT_NOT_FOUND',
210
- message: `Project "${params.projectKey}" not found. Use list_projects to see available projects.`,
211
- },
212
- };
213
- }
214
- // Get next ticket sequence
215
- const sequence = await getNextTicketSequence(project.id);
216
- const ticketKey = `${project.key}-${sequence}`;
217
- const id = randomUUID();
218
- const now = new Date();
219
- // Insert ticket - mark as AI-created
220
- await client.insert(tickets).values({
221
- id,
222
- projectId: project.id,
223
- sequence,
224
- title: params.title,
225
- description: params.description || '',
226
- status: 'backlog',
227
- type: params.type,
228
- priority: params.priority,
229
- labels: params.labels || [],
230
- estimate: params.storyPoints || null,
231
- createdBy: 'ai', // Flag as AI-created
232
- aiAgent: 'glinr-chat', // Track which AI created it
233
- createdAt: now,
234
- updatedAt: now,
235
- });
236
- logger.info(`[GLINR] Created ticket ${ticketKey}: ${params.title}`);
237
- const data = {
238
- id,
239
- key: ticketKey,
240
- title: params.title,
241
- description: params.description,
242
- status: 'backlog',
243
- type: params.type,
244
- priority: params.priority,
245
- labels: params.labels,
246
- storyPoints: params.storyPoints,
247
- projectKey: project.key,
248
- createdAt: now.toISOString(),
249
- url: `/tickets/${id}`,
250
- };
251
- const labelsLine = params.labels?.length ? `\n- Labels: ${params.labels.join(', ')}` : '';
252
- const output = `## Ticket Created\n**${ticketKey}** — ${params.title}\n- Type: ${params.type}\n- Priority: ${params.priority}\n- Status: backlog${labelsLine}`;
253
- return { success: true, data, output };
254
- }
255
- catch (error) {
256
- logger.error('[GLINR] Create ticket error:', error instanceof Error ? error : undefined);
257
- return {
258
- success: false,
259
- error: {
260
- code: 'CREATE_FAILED',
261
- message: error instanceof Error ? error.message : 'Failed to create ticket',
262
- },
263
- };
264
- }
265
- },
266
- };
267
- // =============================================================================
268
- // Create Project Tool
269
- // =============================================================================
270
- export const createProjectTool = {
271
- name: 'create_project',
272
- description: `Create a new project in GLINR. Projects organize tickets under a unique key.`,
273
- category: 'glinr',
274
- securityLevel: 'safe',
275
- allowedHosts: ['sandbox', 'gateway', 'local'],
276
- parameters: CreateProjectParamsSchema,
277
- isAvailable: checkDatabaseAvailability,
278
- examples: [
279
- {
280
- description: 'Create a project',
281
- params: { name: 'Mobile App', key: 'MOBILE', icon: '📱' },
282
- },
283
- ],
284
- async execute(_context, params) {
285
- try {
286
- const client = getDb();
287
- const key = params.key.toUpperCase();
288
- // Check if key exists
289
- const existing = await getProjectByKey(key);
290
- if (existing) {
291
- return {
292
- success: false,
293
- error: {
294
- code: 'KEY_EXISTS',
295
- message: `Project key "${key}" already exists`,
296
- },
297
- };
298
- }
299
- const id = randomUUID();
300
- const now = new Date();
301
- // Get next project sequence
302
- const maxSeq = await client.select({ max: sql `COALESCE(MAX(sequence), 0)` }).from(projects);
303
- const sequence = (maxSeq[0]?.max || 0) + 1;
304
- await client.insert(projects).values({
305
- id,
306
- sequence,
307
- name: params.name,
308
- key,
309
- description: params.description || null,
310
- icon: params.icon || '📁',
311
- color: params.color || '#6366f1',
312
- status: 'active',
313
- createdAt: now,
314
- updatedAt: now,
315
- });
316
- logger.info(`[GLINR] Created project ${key}: ${params.name}`);
317
- const data = {
318
- id,
319
- name: params.name,
320
- key,
321
- description: params.description,
322
- icon: params.icon || '📁',
323
- color: params.color || '#6366f1',
324
- status: 'active',
325
- };
326
- const output = `## Project Created\n**${params.name}** (${key})${params.description ? `\n${params.description}` : ''}`;
327
- return { success: true, data, output };
328
- }
329
- catch (error) {
330
- logger.error('[GLINR] Create project error:', error instanceof Error ? error : undefined);
331
- return {
332
- success: false,
333
- error: {
334
- code: 'CREATE_FAILED',
335
- message: error instanceof Error ? error.message : 'Failed to create project',
336
- },
337
- };
338
- }
339
- },
340
- };
341
- // =============================================================================
342
- // List Tickets Tool
343
- // =============================================================================
344
- export const listTicketsTool = {
345
- name: 'list_tickets',
346
- description: `List and search tickets in GLINR. Can filter by project, status, type, priority, or search text.`,
347
- category: 'glinr',
348
- securityLevel: 'safe',
349
- allowedHosts: ['sandbox', 'gateway', 'local'],
350
- parameters: ListTicketsParamsSchema,
351
- isAvailable: checkDatabaseAvailability,
352
- examples: [
353
- { description: 'List all tickets', params: {} },
354
- { description: 'List bugs in a project', params: { projectKey: 'GLINR', type: 'bug' } },
355
- { description: 'Search tickets', params: { search: 'login' } },
356
- ],
357
- async execute(_context, params) {
358
- try {
359
- const client = getDb();
360
- // Build conditions
361
- const conditions = [];
362
- if (params.projectKey) {
363
- const project = await getProjectByKey(params.projectKey);
364
- if (project) {
365
- conditions.push(eq(tickets.projectId, project.id));
366
- }
367
- }
368
- if (params.status) {
369
- conditions.push(eq(tickets.status, params.status));
370
- }
371
- if (params.type) {
372
- conditions.push(eq(tickets.type, params.type));
373
- }
374
- if (params.priority) {
375
- conditions.push(eq(tickets.priority, params.priority));
376
- }
377
- if (params.search) {
378
- conditions.push(or(like(tickets.title, `%${params.search}%`), like(tickets.description, `%${params.search}%`)));
379
- }
380
- // Execute query
381
- const whereClause = conditions.length > 0 ? and(...conditions) : undefined;
382
- const results = await client
383
- .select({
384
- id: tickets.id,
385
- projectId: tickets.projectId,
386
- sequence: tickets.sequence,
387
- title: tickets.title,
388
- description: tickets.description,
389
- status: tickets.status,
390
- type: tickets.type,
391
- priority: tickets.priority,
392
- labels: tickets.labels,
393
- estimate: tickets.estimate,
394
- createdAt: tickets.createdAt,
395
- })
396
- .from(tickets)
397
- .where(whereClause)
398
- .orderBy(desc(tickets.createdAt))
399
- .limit(params.limit);
400
- // Get project keys for each ticket
401
- const projectIdSet = new Set();
402
- for (const t of results) {
403
- if (t.projectId)
404
- projectIdSet.add(t.projectId);
405
- }
406
- const projectMap = new Map();
407
- for (const projectId of projectIdSet) {
408
- const project = await client.select().from(projects).where(eq(projects.id, projectId)).limit(1);
409
- if (project[0]) {
410
- projectMap.set(projectId, project[0].key);
411
- }
412
- }
413
- const ticketResults = results.map((t) => ({
414
- id: t.id,
415
- key: `${projectMap.get(t.projectId || '') || 'UNK'}-${t.sequence}`,
416
- title: t.title,
417
- description: t.description || undefined,
418
- status: t.status,
419
- type: t.type,
420
- priority: t.priority,
421
- labels: t.labels || undefined,
422
- storyPoints: t.estimate || undefined,
423
- projectKey: projectMap.get(t.projectId || '') || 'UNK',
424
- createdAt: formatDateSafe(t.createdAt),
425
- url: `/tickets/${t.id}`,
426
- }));
427
- // Build markdown output
428
- let output;
429
- if (ticketResults.length === 0) {
430
- output = '## Tickets\n\nNo tickets found matching your criteria.';
431
- }
432
- else {
433
- const rows = ticketResults.map(t => `| ${t.key} | ${t.title} | ${t.status} | ${t.priority} |`);
434
- output = `## Tickets (${ticketResults.length})\n\n| Key | Title | Status | Priority |\n|-----|-------|--------|----------|\n${rows.join('\n')}`;
435
- }
436
- return {
437
- success: true,
438
- data: { tickets: ticketResults, total: ticketResults.length },
439
- output,
440
- };
441
- }
442
- catch (error) {
443
- logger.error('[GLINR] List tickets error:', error instanceof Error ? error : undefined);
444
- return {
445
- success: false,
446
- error: {
447
- code: 'LIST_FAILED',
448
- message: error instanceof Error ? error.message : 'Failed to list tickets',
449
- },
450
- };
451
- }
452
- },
453
- };
454
- // =============================================================================
455
- // List Projects Tool
456
- // =============================================================================
457
- export const listProjectsTool = {
458
- name: 'list_projects',
459
- description: `List all projects in GLINR. **REQUIRED FIRST STEP** before creating tickets - returns project keys needed for create_ticket.
460
-
461
- WORKFLOW: To create a ticket, you MUST:
462
- 1. FIRST call list_projects → get the projectKey (e.g., "GLINR")
463
- 2. THEN call create_ticket with that projectKey
464
-
465
- Example: User says "create a bug ticket" →
466
- - Call list_projects → returns { projects: [{key: "GLINR", ...}] }
467
- - Call create_ticket with projectKey="GLINR", title="...", type="bug"
468
- - Return summary to user`,
469
- category: 'glinr',
470
- securityLevel: 'safe',
471
- allowedHosts: ['sandbox', 'gateway', 'local'],
472
- parameters: ListProjectsParamsSchema,
473
- isAvailable: checkDatabaseAvailability,
474
- examples: [{ description: 'List active projects', params: { status: 'active' } }],
475
- async execute(_context, params) {
476
- try {
477
- const client = getDb();
478
- const conditions = params.status !== 'all' ? [eq(projects.status, params.status === 'archived' ? 'archived' : 'active')] : [];
479
- const results = await client
480
- .select()
481
- .from(projects)
482
- .where(conditions.length > 0 ? and(...conditions) : undefined)
483
- .orderBy(projects.name);
484
- // Get ticket counts
485
- const projectResults = [];
486
- for (const p of results) {
487
- const countResult = await client
488
- .select({ count: sql `COUNT(*)` })
489
- .from(tickets)
490
- .where(eq(tickets.projectId, p.id));
491
- projectResults.push({
492
- id: p.id,
493
- name: p.name,
494
- key: p.key,
495
- description: p.description || undefined,
496
- icon: p.icon || undefined,
497
- color: p.color || undefined,
498
- status: p.status,
499
- ticketCount: countResult[0]?.count || 0,
500
- });
501
- }
502
- // Build hint for AI about next actions
503
- const projectKeys = projectResults.map(p => p.key).join(', ');
504
- const hint = projectResults.length > 0
505
- ? `NEXT STEP: To create a ticket, call create_ticket with one of these projectKeys: ${projectKeys}`
506
- : `No projects found. Create a project first with create_project, then create tickets in it.`;
507
- // Build markdown output
508
- let output;
509
- if (projectResults.length === 0) {
510
- output = '## Projects\n\nNo projects found. Use `create_project` to create one.';
511
- }
512
- else {
513
- const lines = projectResults.map(p => `- **${p.name}** (${p.key}) — ${p.ticketCount ?? 0} tickets`);
514
- output = `## Projects\n\n${lines.join('\n')}`;
515
- }
516
- return {
517
- success: true,
518
- data: { projects: projectResults, hint },
519
- output,
520
- };
521
- }
522
- catch (error) {
523
- logger.error('[GLINR] List projects error:', error instanceof Error ? error : undefined);
524
- return {
525
- success: false,
526
- error: {
527
- code: 'LIST_FAILED',
528
- message: error instanceof Error ? error.message : 'Failed to list projects',
529
- },
530
- };
531
- }
532
- },
533
- };
534
- // =============================================================================
535
- // Update Ticket Tool
536
- // =============================================================================
537
- export const updateTicketTool = {
538
- name: 'update_ticket',
539
- description: `Update an existing ticket's status, priority, description, or other fields.`,
540
- category: 'glinr',
541
- securityLevel: 'safe',
542
- allowedHosts: ['sandbox', 'gateway', 'local'],
543
- parameters: UpdateTicketParamsSchema,
544
- isAvailable: checkDatabaseAvailability,
545
- examples: [
546
- { description: 'Mark ticket done', params: { ticketKey: 'GLINR-123', status: 'done' } },
547
- { description: 'Update priority', params: { ticketKey: 'GLINR-123', priority: 'critical' } },
548
- ],
549
- async execute(_context, params) {
550
- try {
551
- const client = getDb();
552
- // Get existing ticket
553
- const ticket = await getTicketByKey(params.ticketKey);
554
- if (!ticket) {
555
- return {
556
- success: false,
557
- error: {
558
- code: 'TICKET_NOT_FOUND',
559
- message: `Ticket "${params.ticketKey}" not found`,
560
- },
561
- };
562
- }
563
- // Build update object — updatedAt must be Date (schema uses mode: "timestamp")
564
- const updates = {
565
- updatedAt: new Date(),
566
- };
567
- if (params.title !== undefined)
568
- updates.title = params.title;
569
- if (params.description !== undefined)
570
- updates.description = params.description;
571
- if (params.status !== undefined)
572
- updates.status = params.status;
573
- if (params.priority !== undefined)
574
- updates.priority = params.priority;
575
- if (params.type !== undefined)
576
- updates.type = params.type;
577
- if (params.labels !== undefined)
578
- updates.labels = params.labels;
579
- if (params.storyPoints !== undefined)
580
- updates.estimate = params.storyPoints;
581
- await client.update(tickets).set(updates).where(eq(tickets.id, ticket.id));
582
- // Get updated ticket
583
- const updated = await getTicketByKey(params.ticketKey);
584
- logger.info(`[GLINR] Updated ticket ${params.ticketKey}`);
585
- const data = {
586
- id: updated.id,
587
- key: params.ticketKey,
588
- title: updated.title,
589
- description: updated.description || undefined,
590
- status: updated.status,
591
- type: updated.type,
592
- priority: updated.priority,
593
- labels: updated.labels || undefined,
594
- storyPoints: updated.estimate || undefined,
595
- projectKey: updated.projectKey,
596
- createdAt: formatDateSafe(updated.createdAt),
597
- url: `/tickets/${updated.id}`,
598
- };
599
- // Build change summary
600
- const changes = [];
601
- if (params.status !== undefined)
602
- changes.push(`- Status: ${ticket.status} → ${params.status}`);
603
- if (params.priority !== undefined)
604
- changes.push(`- Priority: ${ticket.priority} → ${params.priority}`);
605
- if (params.type !== undefined)
606
- changes.push(`- Type: ${ticket.type} → ${params.type}`);
607
- if (params.title !== undefined)
608
- changes.push(`- Title: ${params.title}`);
609
- if (params.labels !== undefined)
610
- changes.push(`- Labels: ${params.labels.join(', ')}`);
611
- if (params.storyPoints !== undefined)
612
- changes.push(`- Story Points: ${params.storyPoints}`);
613
- const output = `## Ticket Updated\n**${params.ticketKey}** — ${updated.title}\n${changes.join('\n')}`;
614
- return { success: true, data, output };
615
- }
616
- catch (error) {
617
- logger.error('[GLINR] Update ticket error:', error instanceof Error ? error : undefined);
618
- return {
619
- success: false,
620
- error: {
621
- code: 'UPDATE_FAILED',
622
- message: error instanceof Error ? error.message : 'Failed to update ticket',
623
- },
624
- };
625
- }
626
- },
627
- };
628
- // =============================================================================
629
- // Get Ticket Tool
630
- // =============================================================================
631
- export const getTicketTool = {
632
- name: 'get_ticket',
633
- description: `Get details of a specific ticket by its key.`,
634
- category: 'glinr',
635
- securityLevel: 'safe',
636
- allowedHosts: ['sandbox', 'gateway', 'local'],
637
- parameters: GetTicketParamsSchema,
638
- isAvailable: checkDatabaseAvailability,
639
- examples: [{ description: 'Get ticket details', params: { ticketKey: 'GLINR-123' } }],
640
- async execute(_context, params) {
641
- try {
642
- const ticket = await getTicketByKey(params.ticketKey);
643
- if (!ticket) {
644
- return {
645
- success: false,
646
- error: {
647
- code: 'TICKET_NOT_FOUND',
648
- message: `Ticket "${params.ticketKey}" not found`,
649
- },
650
- };
651
- }
652
- const data = {
653
- id: ticket.id,
654
- key: params.ticketKey,
655
- title: ticket.title,
656
- description: ticket.description || undefined,
657
- status: ticket.status,
658
- type: ticket.type,
659
- priority: ticket.priority,
660
- labels: ticket.labels || undefined,
661
- storyPoints: ticket.estimate || undefined,
662
- projectKey: ticket.projectKey,
663
- createdAt: formatDateSafe(ticket.createdAt),
664
- url: `/tickets/${ticket.id}`,
665
- };
666
- const labelsLine = ticket.labels?.length ? `\n- Labels: ${ticket.labels.join(', ')}` : '';
667
- const descLine = ticket.description ? `\n\n${ticket.description}` : '';
668
- const output = `## ${params.ticketKey} — ${ticket.title}\n- Status: ${ticket.status}\n- Type: ${ticket.type}\n- Priority: ${ticket.priority}${labelsLine}${descLine}`;
669
- return { success: true, data, output };
670
- }
671
- catch (error) {
672
- logger.error('[GLINR] Get ticket error:', error instanceof Error ? error : undefined);
673
- return {
674
- success: false,
675
- error: {
676
- code: 'GET_FAILED',
677
- message: error instanceof Error ? error.message : 'Failed to get ticket',
678
- },
679
- };
680
- }
681
- },
682
- };
683
- // =============================================================================
684
- // Exports
685
- // =============================================================================
686
- export const glinrTools = [
687
- createTicketTool,
688
- createProjectTool,
689
- listTicketsTool,
690
- listProjectsTool,
691
- updateTicketTool,
692
- getTicketTool,
693
- ];
694
- //# sourceMappingURL=glinr-ops.js.map