huly-mcp-selfhost 3.1.0 → 3.3.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 (53) hide show
  1. package/.env.example +12 -0
  2. package/CHANGELOG.md +211 -0
  3. package/README.md +178 -327
  4. package/dist/connection.d.ts +8 -0
  5. package/dist/connection.js +13 -1
  6. package/dist/connection.js.map +1 -1
  7. package/dist/guard.d.ts +11 -0
  8. package/dist/guard.js +13 -0
  9. package/dist/guard.js.map +1 -1
  10. package/dist/guides/costs.md +48 -0
  11. package/dist/guides/features.md +49 -0
  12. package/dist/guides/onboarding.md +56 -0
  13. package/dist/guides/workflow.md +57 -0
  14. package/dist/index.js +11 -0
  15. package/dist/index.js.map +1 -1
  16. package/dist/registry.js +2 -0
  17. package/dist/registry.js.map +1 -1
  18. package/dist/resources.d.ts +7 -0
  19. package/dist/resources.js +48 -5
  20. package/dist/resources.js.map +1 -1
  21. package/dist/schemas.d.ts +23 -0
  22. package/dist/schemas.js +29 -6
  23. package/dist/schemas.js.map +1 -1
  24. package/dist/server.d.ts +14 -0
  25. package/dist/server.js +67 -3
  26. package/dist/server.js.map +1 -1
  27. package/dist/tools/agent.d.ts +35 -0
  28. package/dist/tools/agent.js +134 -11
  29. package/dist/tools/agent.js.map +1 -1
  30. package/dist/tools/documents.js +2 -1
  31. package/dist/tools/documents.js.map +1 -1
  32. package/dist/tools/issues.d.ts +3 -0
  33. package/dist/tools/issues.js +35 -20
  34. package/dist/tools/issues.js.map +1 -1
  35. package/dist/tools/organizations.js +2 -1
  36. package/dist/tools/organizations.js.map +1 -1
  37. package/dist/utils/content.d.ts +5 -0
  38. package/dist/utils/content.js +34 -2
  39. package/dist/utils/content.js.map +1 -1
  40. package/dist/utils/meeting.d.ts +13 -0
  41. package/dist/utils/meeting.js +118 -0
  42. package/dist/utils/meeting.js.map +1 -0
  43. package/dist/utils/redact.d.ts +3 -0
  44. package/dist/utils/redact.js +26 -0
  45. package/dist/utils/redact.js.map +1 -0
  46. package/dist/utils/spec.d.ts +10 -0
  47. package/dist/utils/spec.js +90 -0
  48. package/dist/utils/spec.js.map +1 -0
  49. package/dist/utils/storage.js +7 -3
  50. package/dist/utils/storage.js.map +1 -1
  51. package/package.json +17 -8
  52. package/scripts/copy-guides.js +18 -0
  53. package/scripts/setup.js +89 -33
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redact.js","sourceRoot":"","sources":["../../src/utils/redact.ts"],"names":[],"mappings":";;;;AAAA;;;GAGG;AACH,MAAM,GAAG,GAAG,qCAAqC,CAAA;AAEjD,uBAA+B,KAAc;IAC3C,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC,OAAO,CAAC,GAAG,EAAE,KAAK,CAAC,CAAA;IAC/D,IAAI,KAAK,YAAY,KAAK,EAAE,CAAC;QAC3B,MAAM,IAAI,GAAG,IAAI,KAAK,CAAC,aAAa,CAAC,KAAK,CAAC,OAAO,CAAW,CAAC,CAAA;QAC9D,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC,KAAK,IAAI,IAAI,CAAC,CAAC,CAAC,aAAa,CAAC,KAAK,CAAC,KAAK,CAAW,CAAC,CAAC,CAAC,SAAS,CAAA;QACnF,OAAO,IAAI,CAAA;IACb,CAAC;IACD,OAAO,KAAK,CAAA;AACd,CAAC;AAED,6GAA6G;AAC7G;IACE,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;IACzC,MAAM,GAAG,GAAG,CAAC,GAAG,IAAe,EAAQ,EAAE,GAAG,KAAK,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC,CAAA,CAAC,CAAC,CAAA;IAC/E,OAAO,CAAC,GAAG,GAAG,OAAO,CAAC,IAAI,GAAG,OAAO,CAAC,KAAK,GAAG,OAAO,CAAC,IAAI,GAAG,OAAO,CAAC,KAAK,GAAG,GAAG,CAAA;AACjF,CAAC"}
@@ -0,0 +1,10 @@
1
+ export interface SpecTask {
2
+ title: string;
3
+ description?: string;
4
+ }
5
+ export interface SpecPlan {
6
+ source: 'checklist' | 'headings' | 'none';
7
+ tasks: SpecTask[];
8
+ skippedDone: number;
9
+ }
10
+ export declare function planFromMarkup(root: any): SpecPlan;
@@ -0,0 +1,90 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.planFromMarkup = planFromMarkup;
4
+ // Spec → issues: turn a document's structure into planned issues without an LLM round trip.
5
+ // Open checklist items become issues; without a checklist, the sections (lowest heading level
6
+ // that occurs more than once, else level 2/1) become issues with their text as description.
7
+ const markdown_1 = require("./markdown");
8
+ const CHECK = /^\s*\[( |x|X)\]\s+/;
9
+ function childrenOf(node) {
10
+ return Array.isArray(node?.content) ? node.content : [];
11
+ }
12
+ /** Open checklist items: Huly todo/task nodes, or "[ ] text" list items from Markdown imports. */
13
+ function checklist(root) {
14
+ const open = [];
15
+ let done = 0;
16
+ const walk = (node) => {
17
+ const type = String(node?.type ?? '');
18
+ if (type === 'todoItem' || type === 'taskItem') {
19
+ const [first, ...rest] = childrenOf(node);
20
+ const title = (0, markdown_1.extractText)(first ?? node).trim();
21
+ const nested = rest.map(markdown_1.extractText).join('\n').trim();
22
+ if (node.attrs?.checked === true)
23
+ done++;
24
+ else if (title !== '')
25
+ open.push({ title, description: nested !== '' ? nested : undefined });
26
+ return;
27
+ }
28
+ if (type === 'listItem') {
29
+ const [first, ...rest] = childrenOf(node);
30
+ const text = (0, markdown_1.extractText)(first ?? node).trim();
31
+ const m = CHECK.exec(text);
32
+ if (m != null) {
33
+ const nested = rest.map(markdown_1.extractText).join('\n').trim();
34
+ if (m[1] !== ' ')
35
+ done++;
36
+ else
37
+ open.push({ title: text.replace(CHECK, '').trim(), description: nested !== '' ? nested : undefined });
38
+ return;
39
+ }
40
+ }
41
+ for (const c of childrenOf(node))
42
+ walk(c);
43
+ };
44
+ walk(root);
45
+ return { open, done };
46
+ }
47
+ function headings(root) {
48
+ const blocks = childrenOf(root);
49
+ const levels = blocks.filter((b) => b?.type === 'heading').map((b) => Number(b.attrs?.level ?? 1));
50
+ if (levels.length === 0)
51
+ return [];
52
+ const count = (l) => levels.filter((x) => x === l).length;
53
+ // Sections are the most common heading level among 2/3; a single H1 is usually the title.
54
+ const level = [2, 3, 1].find((l) => count(l) > 1) ?? [2, 3, 1].find((l) => count(l) > 0) ?? levels[0];
55
+ const tasks = [];
56
+ let current;
57
+ const flush = () => {
58
+ if (current == null)
59
+ return;
60
+ const body = current.body.join('\n').trim();
61
+ tasks.push({ title: current.title, description: body !== '' ? body.slice(0, 2000) : undefined });
62
+ };
63
+ for (const b of blocks) {
64
+ if (b?.type === 'heading') {
65
+ const l = Number(b.attrs?.level ?? 1);
66
+ if (l === level) {
67
+ flush();
68
+ current = { title: (0, markdown_1.extractText)(b).trim(), body: [] };
69
+ continue;
70
+ }
71
+ if (l < level) {
72
+ flush();
73
+ current = undefined;
74
+ continue;
75
+ }
76
+ }
77
+ if (current != null)
78
+ current.body.push((0, markdown_1.extractText)(b));
79
+ }
80
+ flush();
81
+ return tasks.filter((t) => t.title !== '');
82
+ }
83
+ function planFromMarkup(root) {
84
+ const list = checklist(root);
85
+ if (list.open.length > 0 || list.done > 0)
86
+ return { source: 'checklist', tasks: list.open, skippedDone: list.done };
87
+ const secs = headings(root);
88
+ return { source: secs.length > 0 ? 'headings' : 'none', tasks: secs, skippedDone: 0 };
89
+ }
90
+ //# sourceMappingURL=spec.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spec.js","sourceRoot":"","sources":["../../src/utils/spec.ts"],"names":[],"mappings":"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"}
@@ -24,6 +24,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
24
24
  exports.uploadMarkupBlob = uploadMarkupBlob;
25
25
  exports.uploadBlob = uploadBlob;
26
26
  exports.deleteBlob = deleteBlob;
27
+ /** Uploads can be large files; without a limit a stalled connection hangs the tool call. */
28
+ const UPLOAD_TIMEOUT_MS = 120_000;
27
29
  const CLOUD_DATALAKE_URL = 'https://dl-eu.huly.app';
28
30
  async function uploadMarkupBlob(wsToken, workspaceUuid, blobId, content) {
29
31
  const result = await uploadBlob(wsToken, workspaceUuid, blobId, new Blob([content], { type: 'application/json' }));
@@ -43,7 +45,8 @@ async function uploadViaSelfHostedFront(frontUrl, wsToken, blobId, blob) {
43
45
  const uploadRes = await fetch(uploadUrl, {
44
46
  method: 'POST',
45
47
  headers: { Authorization: `Bearer ${wsToken}` },
46
- body: form
48
+ body: form,
49
+ signal: AbortSignal.timeout(UPLOAD_TIMEOUT_MS)
47
50
  });
48
51
  if (!uploadRes.ok) {
49
52
  const body = await uploadRes.text().catch(() => '');
@@ -63,7 +66,8 @@ async function uploadViaCloudDatalake(workspaceUuid, wsToken, blobId, blob) {
63
66
  const uploadRes = await fetch(uploadUrl, {
64
67
  method: 'POST',
65
68
  headers: { Authorization: `Bearer ${wsToken}` },
66
- body: form
69
+ body: form,
70
+ signal: AbortSignal.timeout(UPLOAD_TIMEOUT_MS)
67
71
  });
68
72
  if (!uploadRes.ok) {
69
73
  const body = await uploadRes.text().catch(() => '');
@@ -77,6 +81,6 @@ async function deleteBlob(wsToken, workspaceUuid, blobId) {
77
81
  const url = frontUrl != null && frontUrl !== ''
78
82
  ? `${frontUrl.replace(/\/$/, '')}/files?file=${encodeURIComponent(blobId)}`
79
83
  : `${CLOUD_DATALAKE_URL}/blob/${encodeURIComponent(workspaceUuid)}/${encodeURIComponent(blobId)}`;
80
- await fetch(url, { method: 'DELETE', headers: { Authorization: `Bearer ${wsToken}` } }).catch(() => { });
84
+ await fetch(url, { method: 'DELETE', headers: { Authorization: `Bearer ${wsToken}` }, signal: AbortSignal.timeout(30_000) }).catch(() => { });
81
85
  }
82
86
  //# sourceMappingURL=storage.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"storage.js","sourceRoot":"","sources":["../../src/utils/storage.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"storage.js","sourceRoot":"","sources":["../../src/utils/storage.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,26 +1,31 @@
1
1
  {
2
2
  "name": "huly-mcp-selfhost",
3
- "version": "3.1.0",
4
- "description": "MCP server for Huly (self-hosted + cloud) — connect Claude Desktop to your Huly workspace via the native WebSocket SDK. Fork of huly-mcp-sdk with self-hosted document/description support, chat, attachments, custom issue statuses, and organizations.",
3
+ "version": "3.3.0",
4
+ "description": "MCP server for Huly (self-hosted and Huly Cloud): 224 tools for Claude and other AI agents — issues, epics, documents, chat, planner, test management — with toolsets for low token use, an agent workflow and a Claude Code plugin.",
5
5
  "keywords": [
6
6
  "huly",
7
+ "huly-selfhost",
7
8
  "mcp",
9
+ "mcp-server",
8
10
  "model-context-protocol",
9
11
  "claude",
12
+ "claude-code",
13
+ "claude-desktop",
14
+ "ai-agents",
10
15
  "project-management",
11
16
  "issue-tracker",
12
- "anthropic"
17
+ "self-hosted"
13
18
  ],
14
- "homepage": "https://github.com/JoKeks2023/huly-mcp#readme",
19
+ "homepage": "https://jokeks2023.github.io/huly-mcp/",
15
20
  "repository": {
16
21
  "type": "git",
17
22
  "url": "git+https://github.com/JoKeks2023/huly-mcp.git"
18
23
  },
19
24
  "mcpName": "io.github.jokeks2023/huly-mcp-selfhost",
20
25
  "license": "EPL-2.0",
21
- "author": "SaiVaraprasad Medapati",
26
+ "author": "Joris Conrad (https://github.com/JoKeks2023)",
22
27
  "contributors": [
23
- "Joris Conrad (self-hosted fork: https://github.com/JoKeks2023/huly-mcp)"
28
+ "SaiVaraprasad Medapati (original huly-mcp-sdk: https://github.com/varaprasadreddy9676/huly-mcp)"
24
29
  ],
25
30
  "bin": {
26
31
  "huly-mcp": "dist/index.js",
@@ -31,13 +36,14 @@
31
36
  "dist/",
32
37
  "scripts/",
33
38
  ".env.example",
34
- "README.md"
39
+ "README.md",
40
+ "CHANGELOG.md"
35
41
  ],
36
42
  "engines": {
37
43
  "node": ">=20"
38
44
  },
39
45
  "scripts": {
40
- "build": "node --max-old-space-size=4096 node_modules/typescript/bin/tsc",
46
+ "build": "node -e \"require('fs').rmSync('dist', { recursive: true, force: true })\" && node --max-old-space-size=4096 node_modules/typescript/bin/tsc && node scripts/copy-guides.js",
41
47
  "dev": "tsx watch src/index.ts",
42
48
  "start": "node dist/index.js",
43
49
  "setup": "node scripts/setup.js",
@@ -70,5 +76,8 @@
70
76
  "overrides": {
71
77
  "dompurify": "^3.4.17",
72
78
  "uuid": "^11.1.1"
79
+ },
80
+ "bugs": {
81
+ "url": "https://github.com/JoKeks2023/huly-mcp/issues"
73
82
  }
74
83
  }
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env node
2
+ // Build step: the plugin skill files are the one source for the agent guides. They are copied to
3
+ // dist/guides/ (without the skill frontmatter) so every MCP client can read them as huly://guide/*.
4
+ 'use strict'
5
+ const fs = require('fs')
6
+ const path = require('path')
7
+
8
+ const root = path.join(__dirname, '..')
9
+ const src = path.join(root, 'plugin', 'skills', 'huly')
10
+ const out = path.join(root, 'dist', 'guides')
11
+ const GUIDES = { workflow: 'SKILL.md', costs: 'costs.md', onboarding: 'onboarding.md', features: 'features.md' }
12
+
13
+ fs.mkdirSync(out, { recursive: true })
14
+ for (const [name, file] of Object.entries(GUIDES)) {
15
+ const text = fs.readFileSync(path.join(src, file), 'utf8').replace(/\r\n/g, '\n').replace(/^---\n[\s\S]*?\n---\n+/, '')
16
+ fs.writeFileSync(path.join(out, `${name}.md`), text)
17
+ }
18
+ console.log(`guides: ${Object.keys(GUIDES).length} copied to dist/guides`)
package/scripts/setup.js CHANGED
@@ -1,18 +1,17 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * One-time setup: authenticate via OTP and save your token to .env
3
+ * One-time setup for SSO accounts (Google/GitHub): log in with a one-time code and get a token.
4
4
  *
5
5
  * Usage:
6
- * node scripts/setup.js
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)
7
9
  *
8
10
  * What it does:
9
- * 1. Sends a one-time code to your email
10
- * 2. You paste the code
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
11
13
  * 3. Lists the workspaces your account can access (discovery test)
12
- * 4. Saves HULY_TOKEN to .env
13
- *
14
- * No workspace is configured: the MCP server discovers all workspaces of the
15
- * account at runtime and every tool takes a `workspace` argument.
14
+ * 4. In a repo checkout: saves HULY_TOKEN to .env. Otherwise: prints what to put where.
16
15
  */
17
16
 
18
17
  'use strict'
@@ -23,18 +22,47 @@ const readline = require('readline')
23
22
 
24
23
  const { getClient } = require('@hcengineering/account-client')
25
24
 
26
- const envFile = path.join(__dirname, '..', '.env')
27
- const accountsUrl = process.env.HULY_ACCOUNTS_URL ?? 'https://account.huly.app'
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'))
28
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 = []
29
35
  function ask (question) {
30
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout })
31
- return new Promise(resolve => rl.question(question, ans => { rl.close(); resolve(ans.trim()) }))
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))
32
44
  }
33
45
 
34
- function writeEnv (token, accountsUrl) {
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) {
35
63
  const lines = [
36
64
  `HULY_TOKEN=${token}`,
37
- accountsUrl !== 'https://account.huly.app' ? `HULY_ACCOUNTS_URL=${accountsUrl}` : null
65
+ urls.accounts !== CLOUD_ACCOUNTS ? `HULY_ACCOUNTS_URL=${urls.accounts}` : null
38
66
  ].filter(Boolean)
39
67
 
40
68
  // Merge with existing .env (don't overwrite unrelated vars)
@@ -51,17 +79,38 @@ function writeEnv (token, accountsUrl) {
51
79
  })
52
80
 
53
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.')
54
95
  }
55
96
 
56
97
  async function main () {
57
98
  console.log('\n🔧 Huly MCP Setup\n')
58
99
 
59
- const email = await ask(' Email address (the one you use for huly.app): ')
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: ')
60
106
  if (!email.includes('@')) { console.error('Invalid email.'); process.exit(1) }
61
107
 
62
- console.log(`\n Sending OTP to ${email}...`)
63
- const unauthClient = getClient(accountsUrl)
64
- await unauthClient.loginOtp(email)
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
+ })
65
114
  console.log(' ✅ Code sent!\n')
66
115
 
67
116
  const code = await ask(' Enter the 6-digit code from your email: ')
@@ -75,7 +124,7 @@ async function main () {
75
124
  }
76
125
 
77
126
  // Discovery test: which workspaces can this account use?
78
- const authedClient = getClient(accountsUrl, loginInfo.token)
127
+ const authedClient = getClient(urls.accounts, loginInfo.token)
79
128
  const workspaces = await authedClient.getUserWorkspaces().catch(e => {
80
129
  console.error(` ❌ Workspace discovery failed: ${e.message}`)
81
130
  process.exit(1)
@@ -86,22 +135,29 @@ async function main () {
86
135
  process.exit(1)
87
136
  }
88
137
 
89
- const hadWorkspace = fs.existsSync(envFile) && /^HULY_WORKSPACE=/m.test(fs.readFileSync(envFile, 'utf8'))
90
- writeEnv(loginInfo.token, accountsUrl)
91
-
92
- console.log(`\n ✅ Setup complete! Token saved to .env`)
93
- console.log(`\n Workspaces available to this account (${workspaces.length}):`)
138
+ console.log(`\n ✅ Logged in. Workspaces available to this account (${workspaces.length}):`)
94
139
  for (const ws of workspaces) {
95
- console.log(` - ${ws.name ?? ws.url} (slug: ${ws.url}, id: ${ws.uuid})${ws.isDisabled ? ' [disabled]' : ''}`)
140
+ console.log(` - ${ws.name ?? ws.url} (slug: ${ws.url})${ws.isDisabled ? ' [disabled]' : ''}`)
96
141
  }
97
- if (hadWorkspace) {
98
- console.log('\n ℹ️ Removed HULY_WORKSPACE from .env — the server now uses all workspaces above.')
99
- console.log(' Re-add it only if you want to restrict this server to a single workspace.')
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)
100
152
  }
101
- console.log('\n In your MCP client, pass the slug as `workspace`, e.g. list_projects(workspace="' + workspaces[0].url + '").\n')
153
+ console.log('\n Optional: pin one workspace with HULY_WORKSPACE (a slug above) — fewer tokens per session.\n')
102
154
  }
103
155
 
104
- main().catch(err => {
105
- console.error('\n❌ Setup failed:', err.message)
106
- process.exit(1)
107
- })
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
+ }