huly-mcp-selfhost 3.3.0 → 3.5.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/.env.example +11 -0
  2. package/CHANGELOG.md +22 -0
  3. package/README.md +126 -17
  4. package/dist/guides/costs.md +2 -1
  5. package/dist/guides/workflow.md +3 -1
  6. package/dist/index.js +105034 -46
  7. package/dist/setup.js +4 -0
  8. package/package.json +12 -11
  9. package/dist/connection.d.ts +0 -78
  10. package/dist/connection.js +0 -271
  11. package/dist/connection.js.map +0 -1
  12. package/dist/env.d.ts +0 -1
  13. package/dist/env.js +0 -17
  14. package/dist/env.js.map +0 -1
  15. package/dist/guard.d.ts +0 -39
  16. package/dist/guard.js +0 -165
  17. package/dist/guard.js.map +0 -1
  18. package/dist/index.d.ts +0 -2
  19. package/dist/index.js.map +0 -1
  20. package/dist/junit-cli.d.ts +0 -8
  21. package/dist/junit-cli.js +0 -106
  22. package/dist/junit-cli.js.map +0 -1
  23. package/dist/registry.d.ts +0 -18
  24. package/dist/registry.js +0 -318
  25. package/dist/registry.js.map +0 -1
  26. package/dist/resources.d.ts +0 -19
  27. package/dist/resources.js +0 -255
  28. package/dist/resources.js.map +0 -1
  29. package/dist/schemas.d.ts +0 -1559
  30. package/dist/schemas.js +0 -1086
  31. package/dist/schemas.js.map +0 -1
  32. package/dist/server.d.ts +0 -29
  33. package/dist/server.js +0 -215
  34. package/dist/server.js.map +0 -1
  35. package/dist/tools/activity.d.ts +0 -10
  36. package/dist/tools/activity.js +0 -62
  37. package/dist/tools/activity.js.map +0 -1
  38. package/dist/tools/agent.d.ts +0 -156
  39. package/dist/tools/agent.js +0 -523
  40. package/dist/tools/agent.js.map +0 -1
  41. package/dist/tools/attachments.d.ts +0 -31
  42. package/dist/tools/attachments.js +0 -61
  43. package/dist/tools/attachments.js.map +0 -1
  44. package/dist/tools/calendar.d.ts +0 -49
  45. package/dist/tools/calendar.js +0 -116
  46. package/dist/tools/calendar.js.map +0 -1
  47. package/dist/tools/cards.d.ts +0 -87
  48. package/dist/tools/cards.js +0 -246
  49. package/dist/tools/cards.js.map +0 -1
  50. package/dist/tools/channels.d.ts +0 -118
  51. package/dist/tools/channels.js +0 -211
  52. package/dist/tools/channels.js.map +0 -1
  53. package/dist/tools/comments.d.ts +0 -42
  54. package/dist/tools/comments.js +0 -77
  55. package/dist/tools/comments.js.map +0 -1
  56. package/dist/tools/components.d.ts +0 -45
  57. package/dist/tools/components.js +0 -107
  58. package/dist/tools/components.js.map +0 -1
  59. package/dist/tools/contacts.d.ts +0 -69
  60. package/dist/tools/contacts.js +0 -130
  61. package/dist/tools/contacts.js.map +0 -1
  62. package/dist/tools/crm.d.ts +0 -61
  63. package/dist/tools/crm.js +0 -117
  64. package/dist/tools/crm.js.map +0 -1
  65. package/dist/tools/documents.d.ts +0 -100
  66. package/dist/tools/documents.js +0 -294
  67. package/dist/tools/documents.js.map +0 -1
  68. package/dist/tools/drive.d.ts +0 -107
  69. package/dist/tools/drive.js +0 -277
  70. package/dist/tools/drive.js.map +0 -1
  71. package/dist/tools/fields.d.ts +0 -33
  72. package/dist/tools/fields.js +0 -124
  73. package/dist/tools/fields.js.map +0 -1
  74. package/dist/tools/generic.d.ts +0 -72
  75. package/dist/tools/generic.js +0 -242
  76. package/dist/tools/generic.js.map +0 -1
  77. package/dist/tools/github.d.ts +0 -21
  78. package/dist/tools/github.js +0 -80
  79. package/dist/tools/github.js.map +0 -1
  80. package/dist/tools/hr.d.ts +0 -97
  81. package/dist/tools/hr.js +0 -150
  82. package/dist/tools/hr.js.map +0 -1
  83. package/dist/tools/inbox.d.ts +0 -23
  84. package/dist/tools/inbox.js +0 -76
  85. package/dist/tools/inbox.js.map +0 -1
  86. package/dist/tools/inventory.d.ts +0 -61
  87. package/dist/tools/inventory.js +0 -103
  88. package/dist/tools/inventory.js.map +0 -1
  89. package/dist/tools/issues.d.ts +0 -112
  90. package/dist/tools/issues.js +0 -474
  91. package/dist/tools/issues.js.map +0 -1
  92. package/dist/tools/labels.d.ts +0 -87
  93. package/dist/tools/labels.js +0 -232
  94. package/dist/tools/labels.js.map +0 -1
  95. package/dist/tools/log-time.d.ts +0 -25
  96. package/dist/tools/log-time.js +0 -95
  97. package/dist/tools/log-time.js.map +0 -1
  98. package/dist/tools/meetings.d.ts +0 -30
  99. package/dist/tools/meetings.js +0 -78
  100. package/dist/tools/meetings.js.map +0 -1
  101. package/dist/tools/members.d.ts +0 -8
  102. package/dist/tools/members.js +0 -33
  103. package/dist/tools/members.js.map +0 -1
  104. package/dist/tools/message-templates.d.ts +0 -54
  105. package/dist/tools/message-templates.js +0 -88
  106. package/dist/tools/message-templates.js.map +0 -1
  107. package/dist/tools/milestones.d.ts +0 -56
  108. package/dist/tools/milestones.js +0 -172
  109. package/dist/tools/milestones.js.map +0 -1
  110. package/dist/tools/organizations.d.ts +0 -37
  111. package/dist/tools/organizations.js +0 -88
  112. package/dist/tools/organizations.js.map +0 -1
  113. package/dist/tools/overview.d.ts +0 -55
  114. package/dist/tools/overview.js +0 -395
  115. package/dist/tools/overview.js.map +0 -1
  116. package/dist/tools/planner.d.ts +0 -60
  117. package/dist/tools/planner.js +0 -145
  118. package/dist/tools/planner.js.map +0 -1
  119. package/dist/tools/process.d.ts +0 -50
  120. package/dist/tools/process.js +0 -137
  121. package/dist/tools/process.js.map +0 -1
  122. package/dist/tools/products.d.ts +0 -17
  123. package/dist/tools/products.js +0 -45
  124. package/dist/tools/products.js.map +0 -1
  125. package/dist/tools/projects.d.ts +0 -41
  126. package/dist/tools/projects.js +0 -93
  127. package/dist/tools/projects.js.map +0 -1
  128. package/dist/tools/qms.d.ts +0 -57
  129. package/dist/tools/qms.js +0 -171
  130. package/dist/tools/qms.js.map +0 -1
  131. package/dist/tools/recruiting.d.ts +0 -112
  132. package/dist/tools/recruiting.js +0 -203
  133. package/dist/tools/recruiting.js.map +0 -1
  134. package/dist/tools/relations.d.ts +0 -72
  135. package/dist/tools/relations.js +0 -192
  136. package/dist/tools/relations.js.map +0 -1
  137. package/dist/tools/search.d.ts +0 -23
  138. package/dist/tools/search.js +0 -60
  139. package/dist/tools/search.js.map +0 -1
  140. package/dist/tools/sharing.d.ts +0 -62
  141. package/dist/tools/sharing.js +0 -149
  142. package/dist/tools/sharing.js.map +0 -1
  143. package/dist/tools/statuses.d.ts +0 -19
  144. package/dist/tools/statuses.js +0 -93
  145. package/dist/tools/statuses.js.map +0 -1
  146. package/dist/tools/surveys.d.ts +0 -22
  147. package/dist/tools/surveys.js +0 -53
  148. package/dist/tools/surveys.js.map +0 -1
  149. package/dist/tools/templates.d.ts +0 -95
  150. package/dist/tools/templates.js +0 -260
  151. package/dist/tools/templates.js.map +0 -1
  152. package/dist/tools/test-automation.d.ts +0 -81
  153. package/dist/tools/test-automation.js +0 -225
  154. package/dist/tools/test-automation.js.map +0 -1
  155. package/dist/tools/testing.d.ts +0 -234
  156. package/dist/tools/testing.js +0 -401
  157. package/dist/tools/testing.js.map +0 -1
  158. package/dist/tools/workspace-admin.d.ts +0 -55
  159. package/dist/tools/workspace-admin.js +0 -117
  160. package/dist/tools/workspace-admin.js.map +0 -1
  161. package/dist/tools/workspaces.d.ts +0 -6
  162. package/dist/tools/workspaces.js +0 -19
  163. package/dist/tools/workspaces.js.map +0 -1
  164. package/dist/utils/attributes.d.ts +0 -37
  165. package/dist/utils/attributes.js +0 -188
  166. package/dist/utils/attributes.js.map +0 -1
  167. package/dist/utils/classes.d.ts +0 -51
  168. package/dist/utils/classes.js +0 -73
  169. package/dist/utils/classes.js.map +0 -1
  170. package/dist/utils/content.d.ts +0 -11
  171. package/dist/utils/content.js +0 -86
  172. package/dist/utils/content.js.map +0 -1
  173. package/dist/utils/errors.d.ts +0 -11
  174. package/dist/utils/errors.js +0 -24
  175. package/dist/utils/errors.js.map +0 -1
  176. package/dist/utils/files.d.ts +0 -5
  177. package/dist/utils/files.js +0 -50
  178. package/dist/utils/files.js.map +0 -1
  179. package/dist/utils/format.d.ts +0 -8
  180. package/dist/utils/format.js +0 -57
  181. package/dist/utils/format.js.map +0 -1
  182. package/dist/utils/junit.d.ts +0 -13
  183. package/dist/utils/junit.js +0 -81
  184. package/dist/utils/junit.js.map +0 -1
  185. package/dist/utils/markdown.d.ts +0 -14
  186. package/dist/utils/markdown.js +0 -149
  187. package/dist/utils/markdown.js.map +0 -1
  188. package/dist/utils/meeting.d.ts +0 -13
  189. package/dist/utils/meeting.js +0 -118
  190. package/dist/utils/meeting.js.map +0 -1
  191. package/dist/utils/people.d.ts +0 -25
  192. package/dist/utils/people.js +0 -80
  193. package/dist/utils/people.js.map +0 -1
  194. package/dist/utils/redact.d.ts +0 -3
  195. package/dist/utils/redact.js +0 -26
  196. package/dist/utils/redact.js.map +0 -1
  197. package/dist/utils/resolve.d.ts +0 -17
  198. package/dist/utils/resolve.js +0 -54
  199. package/dist/utils/resolve.js.map +0 -1
  200. package/dist/utils/spec.d.ts +0 -10
  201. package/dist/utils/spec.js +0 -90
  202. package/dist/utils/spec.js.map +0 -1
  203. package/dist/utils/storage.d.ts +0 -7
  204. package/dist/utils/storage.js +0 -86
  205. package/dist/utils/storage.js.map +0 -1
  206. package/dist/utils/tasks.d.ts +0 -22
  207. package/dist/utils/tasks.js +0 -81
  208. package/dist/utils/tasks.js.map +0 -1
  209. package/dist/watch.d.ts +0 -62
  210. package/dist/watch.js +0 -405
  211. package/dist/watch.js.map +0 -1
  212. package/scripts/cleanup-issues.js +0 -141
  213. package/scripts/copy-guides.js +0 -18
  214. package/scripts/demo.js +0 -201
  215. package/scripts/import-csv.js +0 -250
  216. package/scripts/sample-tasks.csv +0 -11
  217. package/scripts/setup.js +0 -163
package/scripts/demo.js DELETED
@@ -1,201 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * Live demo of huly-mcp-sdk tools against a real Huly workspace.
4
- * Runs through: list projects → create issue → add comment → add label →
5
- * update issue → add relation → get issue → delete issue
6
- */
7
-
8
- 'use strict'
9
-
10
- const fs = require('fs')
11
- const path = require('path')
12
-
13
- // Load .env
14
- const envPath = path.join(__dirname, '..', '.env')
15
- if (fs.existsSync(envPath)) {
16
- for (const line of fs.readFileSync(envPath, 'utf8').split('\n')) {
17
- const m = line.match(/^([^#=\s]+)\s*=\s*(.*)$/)
18
- if (m) process.env[m[1]] = m[2].trim()
19
- }
20
- }
21
-
22
- // Target workspace (these scripts write data, so it must be explicit):
23
- // --workspace=<slug|name|id>, or HULY_WORKSPACE as a fallback.
24
- const workspace = process.argv.find(a => a.startsWith('--workspace='))?.slice('--workspace='.length) || process.env.HULY_WORKSPACE
25
- if (!workspace) {
26
- console.error('❌ No workspace given. Pass --workspace=<slug> (see list_workspaces / npm run setup).')
27
- process.exit(1)
28
- }
29
-
30
- const distDir = path.join(__dirname, '..', 'dist')
31
- const { getConnection, closeConnection } = require(path.join(distDir, 'connection'))
32
- const tracker = require('@hcengineering/tracker')
33
- const task = require('@hcengineering/task')
34
- const tags = require('@hcengineering/tags')
35
- const { generateId, SortingOrder } = require('@hcengineering/core')
36
- const { makeRank } = require('@hcengineering/rank')
37
-
38
- const GREEN = '\x1b[32m'
39
- const CYAN = '\x1b[36m'
40
- const YELLOW = '\x1b[33m'
41
- const BOLD = '\x1b[1m'
42
- const RESET = '\x1b[0m'
43
-
44
- function header (text) {
45
- console.log(`\n${BOLD}${CYAN}━━━ ${text} ━━━${RESET}`)
46
- }
47
- function ok (text) { console.log(`${GREEN}✅ ${text}${RESET}`) }
48
- function info (text) { console.log(`${YELLOW} ${text}${RESET}`) }
49
- function print (text) { console.log(` ${text}`) }
50
-
51
- async function main () {
52
- console.log(`\n${BOLD}🚀 huly-mcp-sdk live demo${RESET}`)
53
- console.log(` Workspace: ${workspace}\n`)
54
-
55
- const client = await getConnection(workspace)
56
- ok('Connected to Huly')
57
-
58
- // ── 1. List projects ──────────────────────────────────────────────────────
59
- header('1 / 8 — list_projects')
60
- const projects = await client.findAll(tracker.default.class.Project, {})
61
- ok(`Found ${projects.length} project(s)`)
62
- for (const p of projects) {
63
- print(`• ${BOLD}${p.identifier}${RESET} ${p.name}`)
64
- }
65
-
66
- // Pick first project for the rest of the demo
67
- const project = projects[0]
68
- if (project == null) { console.error('No projects found — aborting.'); return }
69
- info(`Using project: ${project.identifier}`)
70
-
71
- // ── 2. List issues (top 5) ────────────────────────────────────────────────
72
- header('2 / 8 — list_issues')
73
- const issues = await client.findAll(
74
- tracker.default.class.Issue,
75
- { space: project._id },
76
- { limit: 5, sort: { modifiedOn: SortingOrder.Descending } }
77
- )
78
- const statuses = await client.findAll(tracker.default.class.IssueStatus, {})
79
- const statusMap = new Map(statuses.map(s => [s._id, s.name]))
80
- ok(`Showing 5 most-recent issues in ${project.identifier}`)
81
- for (const i of issues) {
82
- print(`• ${BOLD}${i.identifier}${RESET} [${statusMap.get(i.status) ?? '?'}] ${i.title}`)
83
- }
84
-
85
- // ── 3. Create issue ───────────────────────────────────────────────────────
86
- header('3 / 8 — create_issue')
87
- const incResult = await client.updateDoc(
88
- tracker.default.class.Project, project.space, project._id,
89
- { $inc: { sequence: 1 } }, true
90
- )
91
- const issueNumber = incResult?.object?.sequence ?? project.sequence + 1
92
- const identifier = `${project.identifier}-${issueNumber}`
93
-
94
- const defaultStatus = project.defaultIssueStatus != null
95
- ? (statuses.find(s => s._id === project.defaultIssueStatus) ?? statuses[0])
96
- : statuses[0]
97
- const kind = await client.findOne(task.default.class.TaskType,
98
- project.type != null ? { parent: project.type } : {})
99
- const lastIssue = await client.findOne(
100
- tracker.default.class.Issue, { space: project._id },
101
- { sort: { rank: SortingOrder.Descending } }
102
- )
103
- const rank = makeRank(lastIssue?.rank, undefined)
104
-
105
- const issueId = generateId()
106
- await client.addCollection(
107
- tracker.default.class.Issue, project._id,
108
- tracker.default.ids.NoParent, tracker.default.class.Issue, 'subIssues',
109
- {
110
- title: '🧪 huly-mcp-sdk demo issue',
111
- description: null,
112
- status: defaultStatus._id,
113
- priority: 2, // High
114
- number: issueNumber, identifier, rank,
115
- kind: kind._id,
116
- comments: 0, subIssues: 0,
117
- dueDate: null, assignee: null, component: null, milestone: null,
118
- parents: [], remainingTime: 0, estimation: 0,
119
- reportedTime: 0, reports: 0, childInfo: [], relations: []
120
- },
121
- issueId
122
- )
123
- ok(`Created ${BOLD}${identifier}${RESET}: 🧪 huly-mcp-sdk demo issue`)
124
-
125
- // ── 4. Add comment ────────────────────────────────────────────────────────
126
- header('4 / 8 — add_comment')
127
- const chunter = require('@hcengineering/chunter')
128
- await client.addCollection(
129
- chunter.default.class.ChatMessage, project._id,
130
- issueId, tracker.default.class.Issue, 'comments',
131
- { message: 'This issue was created by the **huly-mcp-sdk** demo script 🎉', attachments: 0 }
132
- )
133
- ok(`Added comment to ${identifier}`)
134
-
135
- // ── 5. Add label ──────────────────────────────────────────────────────────
136
- header('5 / 8 — add_label')
137
- const core = require('@hcengineering/core')
138
- let label = await client.findOne(tags.default.class.TagElement, {
139
- targetClass: tracker.default.class.Issue, title: 'demo'
140
- })
141
- if (label == null) {
142
- const cat = await client.findOne(tags.default.class.TagCategory, {
143
- targetClass: tracker.default.class.Issue
144
- })
145
- const labelId = generateId()
146
- await client.createDoc(
147
- tags.default.class.TagElement, core.default.space.Workspace,
148
- {
149
- title: 'demo', targetClass: tracker.default.class.Issue,
150
- description: '', color: 0x6366f1,
151
- category: cat?._id ?? tags.default.category.NoCategory, refCount: 0
152
- }, labelId
153
- )
154
- label = await client.findOne(tags.default.class.TagElement, { _id: labelId })
155
- info('Auto-created label "demo" (color: #6366f1)')
156
- }
157
- const issue = await client.findOne(tracker.default.class.Issue, { identifier })
158
- await client.addCollection(
159
- tags.default.class.TagReference, issue.space, issue._id,
160
- tracker.default.class.Issue, 'labels',
161
- { tag: label._id, title: label.title, color: label.color }
162
- )
163
- ok(`Labeled ${identifier} with "demo"`)
164
-
165
- // ── 6. Update issue ───────────────────────────────────────────────────────
166
- header('6 / 8 — update_issue')
167
- const inProgress = statuses.find(s => s.name.toLowerCase().includes('progress')) ?? statuses[1] ?? statuses[0]
168
- await client.updateDoc(
169
- tracker.default.class.Issue, issue.space, issue._id,
170
- { status: inProgress._id, priority: 1 /* Urgent */ }
171
- )
172
- ok(`Updated ${identifier} → status: "${inProgress.name}", priority: Urgent`)
173
-
174
- // ── 7. Get issue (full details) ───────────────────────────────────────────
175
- header('7 / 8 — get_issue')
176
- const fresh = await client.findOne(tracker.default.class.Issue, { identifier })
177
- const freshStatus = statusMap.get(fresh.status) ?? 'Unknown'
178
- const PRIORITY = ['No priority', 'Urgent', 'High', 'Medium', 'Low']
179
- ok(`Full details for ${identifier}`)
180
- print(` Title: ${fresh.title}`)
181
- print(` Status: ${freshStatus}`)
182
- print(` Priority: ${PRIORITY[fresh.priority] ?? fresh.priority}`)
183
- print(` Comments: ${fresh.comments}`)
184
-
185
- // ── 8. Delete issue (cleanup) ─────────────────────────────────────────────
186
- header('8 / 8 — delete_issue (cleanup)')
187
- await client.removeCollection(
188
- tracker.default.class.Issue, fresh.space, fresh._id,
189
- fresh.attachedTo, fresh.attachedToClass, fresh.collection
190
- )
191
- ok(`Deleted ${identifier} — workspace is clean`)
192
-
193
- await closeConnection()
194
-
195
- console.log(`\n${BOLD}${GREEN}🎉 Demo complete! All 8 tools exercised successfully.${RESET}\n`)
196
- }
197
-
198
- main().catch(err => {
199
- console.error('\n❌ Demo failed:', err.message)
200
- process.exit(1)
201
- })
@@ -1,250 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * Bulk-import issues from a CSV file into a Huly tracker project.
4
- *
5
- * Usage:
6
- * node scripts/import-csv.js <csv-file> <project-identifier>
7
- *
8
- * Example:
9
- * node scripts/import-csv.js tasks.csv PROJ
10
- *
11
- * CSV format (first row = headers):
12
- * title,priority,status,dueDate
13
- * Fix login bug,High,In Progress,2025-04-01
14
- * Add dark mode,Medium,,
15
- *
16
- * Required columns:
17
- * title — issue title (required)
18
- *
19
- * Optional columns:
20
- * priority — Urgent | High | Medium | Low | No priority (default: No priority)
21
- * status — name of an existing status in the project (default: project default)
22
- * dueDate — YYYY-MM-DD (default: none)
23
- *
24
- * Auth (set ONE option as env vars):
25
- * Option A (SSO/Google/GitHub):
26
- * HULY_TOKEN=eyJ... — token from DevTools → Application → IndexedDB database name
27
- *
28
- * Option B (email + password):
29
- * HULY_EMAIL=you@example.com
30
- * HULY_PASSWORD=yourpassword
31
- *
32
- * Target workspace (required — one account can have several):
33
- * --workspace=your-workspace-slug (or HULY_WORKSPACE=your-workspace-slug)
34
- */
35
-
36
- 'use strict'
37
-
38
- const fs = require('fs')
39
- const path = require('path')
40
-
41
- // Target workspace (these scripts write data, so it must be explicit):
42
- // --workspace=<slug|name|id>, or HULY_WORKSPACE as a fallback.
43
- const workspace = process.argv.find(a => a.startsWith('--workspace='))?.slice('--workspace='.length) || process.env.HULY_WORKSPACE
44
- if (!workspace) {
45
- console.error('❌ No workspace given. Pass --workspace=<slug> (see list_workspaces / npm run setup).')
46
- process.exit(1)
47
- }
48
-
49
- // ── Load compiled huly-mcp modules ──────────────────────────────────────────
50
- const distDir = path.join(__dirname, '..', 'dist')
51
- if (!fs.existsSync(path.join(distDir, 'connection.js'))) {
52
- console.error('❌ dist/ not found. Run `npm run build` first.')
53
- process.exit(1)
54
- }
55
- const { getConnection, closeConnection } = require(path.join(distDir, 'connection'))
56
- const tracker = require('@hcengineering/tracker')
57
- const task = require('@hcengineering/task')
58
- const { SortingOrder, generateId } = require('@hcengineering/core')
59
- const { makeRank } = require('@hcengineering/rank')
60
-
61
- // ── Parse CLI args ────────────────────────────────────────────────────────────
62
- const [,, csvFile, projectIdentifier] = process.argv
63
- if (!csvFile || !projectIdentifier) {
64
- console.error('Usage: node scripts/import-csv.js <csv-file> <project-identifier> --workspace=<slug>')
65
- process.exit(1)
66
- }
67
- if (!fs.existsSync(csvFile)) {
68
- console.error(`❌ File not found: ${csvFile}`)
69
- process.exit(1)
70
- }
71
-
72
- // ── CSV parser (no dependencies) ─────────────────────────────────────────────
73
- function parseCsv (text) {
74
- const lines = text.replace(/\r\n/g, '\n').replace(/\r/g, '\n').split('\n').filter(Boolean)
75
- if (lines.length < 2) return []
76
-
77
- const headers = lines[0].split(',').map(h => h.trim().toLowerCase())
78
- return lines.slice(1).map(line => {
79
- // Handle quoted fields
80
- const fields = []
81
- let current = ''
82
- let inQuotes = false
83
- for (const ch of line) {
84
- if (ch === '"') { inQuotes = !inQuotes }
85
- else if (ch === ',' && !inQuotes) { fields.push(current.trim()); current = '' }
86
- else { current += ch }
87
- }
88
- fields.push(current.trim())
89
- return Object.fromEntries(headers.map((h, i) => [h, fields[i] ?? '']))
90
- })
91
- }
92
-
93
- // ── Priority mapping ──────────────────────────────────────────────────────────
94
- const PRIORITY_MAP = {
95
- 'urgent': 1, // IssuePriority.Urgent
96
- 'high': 2, // IssuePriority.High
97
- 'medium': 3, // IssuePriority.Medium
98
- 'low': 4, // IssuePriority.Low
99
- 'no priority': 0,
100
- '': 0
101
- }
102
-
103
- function parsePriority (str) {
104
- const key = (str || '').toLowerCase().trim()
105
- return PRIORITY_MAP[key] ?? 0
106
- }
107
-
108
- // ── Main import ───────────────────────────────────────────────────────────────
109
- async function main () {
110
- console.log(`\n📋 Huly CSV Importer`)
111
- console.log(` File: ${csvFile}`)
112
- console.log(` Project: ${projectIdentifier}`)
113
- console.log(` Workspace: ${workspace}\n`)
114
-
115
- // Parse CSV
116
- const rows = parseCsv(fs.readFileSync(csvFile, 'utf8'))
117
- const validRows = rows.filter(r => r.title && r.title.trim())
118
- console.log(`📂 Found ${rows.length} rows, ${validRows.length} with titles.\n`)
119
-
120
- if (validRows.length === 0) {
121
- console.error('❌ No valid rows found. Make sure CSV has a "title" column.')
122
- process.exit(1)
123
- }
124
-
125
- // Connect to Huly
126
- console.log('🔌 Connecting to Huly...')
127
- const client = await getConnection(workspace)
128
- console.log('✅ Connected.\n')
129
-
130
- // Find project
131
- const project = await client.findOne(tracker.default.class.Project, { identifier: projectIdentifier })
132
- if (project == null) {
133
- console.error(`❌ Project '${projectIdentifier}' not found.`)
134
- await closeConnection()
135
- process.exit(1)
136
- }
137
-
138
- // Load statuses — Huly stores them in project space OR global model space
139
- let statuses = await client.findAll(tracker.default.class.IssueStatus, { space: project._id })
140
- if (statuses.length === 0) {
141
- // Fall back to global model-level statuses (used by default project types)
142
- statuses = await client.findAll(tracker.default.class.IssueStatus, {})
143
- }
144
- const statusMap = new Map(statuses.map(s => [s.name.toLowerCase(), s]))
145
- const defaultStatus = project.defaultIssueStatus != null
146
- ? (statuses.find(s => s._id === project.defaultIssueStatus) ?? statuses[0])
147
- : statuses[0]
148
-
149
- // Find TaskType
150
- const kind = await client.findOne(task.default.class.TaskType,
151
- project.type != null ? { parent: project.type } : {})
152
- if (kind == null) {
153
- console.error('❌ Could not find a TaskType for this project.')
154
- await closeConnection()
155
- process.exit(1)
156
- }
157
-
158
- console.log(`📁 Project: ${project.name}`)
159
- console.log(`📊 Available statuses: ${statuses.map(s => s.name).join(', ')}`)
160
- console.log(`🎯 Default status: ${defaultStatus.name}\n`)
161
-
162
- // Import rows
163
- let created = 0
164
- let failed = 0
165
-
166
- for (let i = 0; i < validRows.length; i++) {
167
- const row = validRows[i]
168
- const num = i + 1
169
-
170
- process.stdout.write(`[${num}/${validRows.length}] Creating: "${row.title.slice(0, 60)}"... `)
171
-
172
- try {
173
- // Increment sequence
174
- const incResult = await client.updateDoc(
175
- tracker.default.class.Project,
176
- project.space,
177
- project._id,
178
- { $inc: { sequence: 1 } },
179
- true
180
- )
181
- const issueNumber = incResult?.object?.sequence ?? project.sequence + 1
182
- const identifier = `${project.identifier}-${issueNumber}`
183
-
184
- // Resolve status
185
- const statusName = (row.status || '').toLowerCase().trim()
186
- const status = statusName ? (statusMap.get(statusName) ?? defaultStatus) : defaultStatus
187
-
188
- // Compute rank
189
- const lastIssue = await client.findOne(
190
- tracker.default.class.Issue,
191
- { space: project._id },
192
- { sort: { rank: SortingOrder.Descending } }
193
- )
194
- const rank = makeRank(lastIssue?.rank, undefined)
195
-
196
- // Create issue
197
- const issueId = generateId()
198
- await client.addCollection(
199
- tracker.default.class.Issue,
200
- project._id,
201
- tracker.default.ids.NoParent,
202
- tracker.default.class.Issue,
203
- 'subIssues',
204
- {
205
- title: row.title.trim(),
206
- description: null,
207
- status: status._id,
208
- priority: parsePriority(row.priority),
209
- number: issueNumber,
210
- identifier,
211
- rank,
212
- kind: kind._id,
213
- comments: 0,
214
- subIssues: 0,
215
- dueDate: row.duedate || row.dueDate
216
- ? new Date(row.duedate || row.dueDate).getTime()
217
- : null,
218
- assignee: null,
219
- component: null,
220
- milestone: null,
221
- parents: [],
222
- remainingTime: 0,
223
- estimation: 0,
224
- reportedTime: 0,
225
- reports: 0,
226
- childInfo: [],
227
- relations: []
228
- },
229
- issueId
230
- )
231
-
232
- console.log(`✅ ${identifier}`)
233
- created++
234
- } catch (err) {
235
- console.log(`❌ ERROR: ${err.message}`)
236
- failed++
237
- }
238
- }
239
-
240
- await closeConnection()
241
-
242
- console.log(`\n${'─'.repeat(50)}`)
243
- console.log(`✅ Created: ${created} ❌ Failed: ${failed}`)
244
- console.log(`${'─'.repeat(50)}\n`)
245
- }
246
-
247
- main().catch(err => {
248
- console.error('\n❌ Fatal error:', err.message)
249
- process.exit(1)
250
- })
@@ -1,11 +0,0 @@
1
- title,priority,status,dueDate
2
- Fix login timeout bug,High,In Progress,2025-04-01
3
- Add dark mode support,Medium,,
4
- Update onboarding flow,Low,,2025-05-15
5
- Write API documentation,Medium,,
6
- Fix mobile layout issues,High,,
7
- Performance optimization for dashboard,Urgent,,2025-03-31
8
- Add two-factor authentication,High,,
9
- Update privacy policy page,Low,,
10
- Fix broken image uploads,High,In Progress,
11
- Improve search functionality,Medium,,
package/scripts/setup.js DELETED
@@ -1,163 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * One-time setup for SSO accounts (Google/GitHub): log in with a one-time code and get a token.
4
- *
5
- * Usage:
6
- * npx huly-mcp-selfhost setup (anywhere — prints the token to paste into your client)
7
- * npm run setup (in a checkout of this repo — also saves it to .env)
8
- * ... setup --url https://huly.example.com (self-hosted; or set HULY_ACCOUNTS_URL)
9
- *
10
- * What it does:
11
- * 1. Asks for your Huly address (empty = Huly Cloud) unless HULY_ACCOUNTS_URL / --url is given
12
- * 2. Sends a one-time code to your email; you paste the code
13
- * 3. Lists the workspaces your account can access (discovery test)
14
- * 4. In a repo checkout: saves HULY_TOKEN to .env. Otherwise: prints what to put where.
15
- */
16
-
17
- 'use strict'
18
-
19
- const fs = require('fs')
20
- const path = require('path')
21
- const readline = require('readline')
22
-
23
- const { getClient } = require('@hcengineering/account-client')
24
-
25
- const CLOUD_ACCOUNTS = 'https://account.huly.app'
26
- const root = path.join(__dirname, '..')
27
- const envFile = path.join(root, '.env')
28
- // Started via npx the package sits in the npm cache: a .env there would be lost and never read.
29
- const isCheckout = fs.existsSync(path.join(root, '.git'))
30
-
31
- // One interface for the whole run: a new one per question would drop lines already buffered (piped input).
32
- let rl
33
- const lines = []
34
- const waiting = []
35
- function ask (question) {
36
- if (rl == null) {
37
- rl = readline.createInterface({ input: process.stdin })
38
- rl.on('line', line => { const next = waiting.shift(); if (next != null) next(line.trim()); else lines.push(line.trim()) })
39
- rl.on('close', () => { while (waiting.length > 0) waiting.shift()('') })
40
- }
41
- process.stdout.write(question)
42
- if (lines.length > 0) return Promise.resolve(lines.shift())
43
- return new Promise(resolve => waiting.push(resolve))
44
- }
45
-
46
- /** "https://huly.example.com", ".../_accounts" or empty (cloud) → { accounts, front } */
47
- function resolveUrls (input) {
48
- const value = (input ?? '').trim().replace(/\/+$/, '')
49
- if (value === '' || /(^|\/\/)(app\.)?huly\.app$/.test(value) || value === CLOUD_ACCOUNTS) return { accounts: CLOUD_ACCOUNTS, front: undefined }
50
- if (!/^https?:\/\//.test(value)) throw new Error(`Not a URL: ${value} (expected e.g. https://huly.example.com)`)
51
- if (value.endsWith('/_accounts')) return { accounts: value, front: value.slice(0, -'/_accounts'.length) }
52
- return { accounts: `${value}/_accounts`, front: value }
53
- }
54
-
55
- function urlArg (argv) {
56
- const i = argv.indexOf('--url')
57
- if (i >= 0) return argv[i + 1] ?? ''
58
- const eq = argv.find(a => a.startsWith('--url='))
59
- return eq != null ? eq.slice('--url='.length) : undefined
60
- }
61
-
62
- function writeEnv (token, urls) {
63
- const lines = [
64
- `HULY_TOKEN=${token}`,
65
- urls.accounts !== CLOUD_ACCOUNTS ? `HULY_ACCOUNTS_URL=${urls.accounts}` : null
66
- ].filter(Boolean)
67
-
68
- // Merge with existing .env (don't overwrite unrelated vars)
69
- let existing = ''
70
- try {
71
- existing = fs.readFileSync(envFile, 'utf8')
72
- } catch (e) {
73
- if (e.code !== 'ENOENT') throw e
74
- }
75
-
76
- const keep = existing.split('\n').filter(line => {
77
- const key = line.split('=')[0]
78
- return !['HULY_TOKEN', 'HULY_WORKSPACE', 'HULY_EMAIL', 'HULY_PASSWORD', 'HULY_ACCOUNTS_URL'].includes(key)
79
- })
80
-
81
- fs.writeFileSync(envFile, [...keep.filter(Boolean), ...lines].join('\n') + '\n')
82
- return /^HULY_WORKSPACE=/m.test(existing)
83
- }
84
-
85
- function printConfig (token, urls) {
86
- console.log('\n Put these values into your MCP client:')
87
- console.log(' Claude Code plugin: /plugin → huly → configure (fields "Huly token", "Accounts URL", "Front URL")')
88
- console.log(' Other clients: the "env" block of the huly server\n')
89
- console.log(` HULY_TOKEN=${token}`)
90
- if (urls.accounts !== CLOUD_ACCOUNTS) {
91
- console.log(` HULY_ACCOUNTS_URL=${urls.accounts}`)
92
- console.log(` HULY_FRONT_URL=${urls.front}`)
93
- }
94
- console.log('\n Keep the token secret — it logs in as you.')
95
- }
96
-
97
- async function main () {
98
- console.log('\n🔧 Huly MCP Setup\n')
99
-
100
- const fromEnv = (process.env.HULY_ACCOUNTS_URL ?? '').trim()
101
- const given = urlArg(process.argv.slice(2)) ?? (fromEnv !== '' ? fromEnv : undefined)
102
- const urls = resolveUrls(given ?? await ask(' Huly address (empty = Huly Cloud, e.g. https://huly.example.com): '))
103
- if (urls.accounts !== CLOUD_ACCOUNTS) console.log(` Accounts service: ${urls.accounts}`)
104
-
105
- const email = await ask(' Email address of your Huly account: ')
106
- if (!email.includes('@')) { console.error('Invalid email.'); process.exit(1) }
107
-
108
- console.log(`\n Sending a one-time code to ${email}...`)
109
- const unauthClient = getClient(urls.accounts)
110
- await unauthClient.loginOtp(email).catch(e => {
111
- const cause = e.cause?.code ?? e.message
112
- throw new Error(`Could not reach the Huly accounts service at ${urls.accounts} (${cause}). Check the address — self-hosted: your Huly URL, e.g. https://huly.example.com`)
113
- })
114
- console.log(' ✅ Code sent!\n')
115
-
116
- const code = await ask(' Enter the 6-digit code from your email: ')
117
-
118
- console.log('\n Validating...')
119
- const loginInfo = await unauthClient.validateOtp(email, code.trim())
120
-
121
- if (!loginInfo?.token) {
122
- console.error(' ❌ Invalid or expired code. Run setup again.')
123
- process.exit(1)
124
- }
125
-
126
- // Discovery test: which workspaces can this account use?
127
- const authedClient = getClient(urls.accounts, loginInfo.token)
128
- const workspaces = await authedClient.getUserWorkspaces().catch(e => {
129
- console.error(` ❌ Workspace discovery failed: ${e.message}`)
130
- process.exit(1)
131
- })
132
-
133
- if (workspaces.length === 0) {
134
- console.error(' ❌ This account has no accessible workspaces.')
135
- process.exit(1)
136
- }
137
-
138
- console.log(`\n ✅ Logged in. Workspaces available to this account (${workspaces.length}):`)
139
- for (const ws of workspaces) {
140
- console.log(` - ${ws.name ?? ws.url} (slug: ${ws.url})${ws.isDisabled ? ' [disabled]' : ''}`)
141
- }
142
-
143
- if (isCheckout) {
144
- const hadWorkspace = writeEnv(loginInfo.token, urls)
145
- console.log('\n Token saved to .env')
146
- if (hadWorkspace) {
147
- console.log(' ℹ️ Removed HULY_WORKSPACE from .env — the server now uses all workspaces above.')
148
- console.log(' Re-add it only if you want to restrict this server to a single workspace.')
149
- }
150
- } else {
151
- printConfig(loginInfo.token, urls)
152
- }
153
- console.log('\n Optional: pin one workspace with HULY_WORKSPACE (a slug above) — fewer tokens per session.\n')
154
- }
155
-
156
- module.exports = { resolveUrls, urlArg }
157
-
158
- if (require.main === module || process.env.HULY_SETUP_RUN === '1') {
159
- main().then(() => rl?.close()).catch(err => {
160
- console.error('\n❌ Setup failed:', err.message)
161
- process.exit(1)
162
- })
163
- }