@gethmy/mcp 2.22.0 → 2.24.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.
- package/README.md +14 -14
- package/dist/cli.js +216 -70
- package/dist/index.js +181 -45
- package/dist/lib/api-client.js +78 -26
- package/dist/lib/config.js +79 -24
- package/dist/lib/oauth-refresh.js +76 -24
- package/package.json +2 -2
- package/src/api-client.ts +15 -0
- package/src/cli.ts +25 -14
- package/src/config.ts +201 -27
- package/src/remote.ts +34 -6
- package/src/server.ts +165 -12
- package/src/tui/setup.ts +21 -6
package/dist/cli.js
CHANGED
|
@@ -20,7 +20,7 @@ var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
|
20
20
|
// src/config.ts
|
|
21
21
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
22
22
|
import { homedir } from "node:os";
|
|
23
|
-
import { join } from "node:path";
|
|
23
|
+
import { dirname, join, parse, resolve } from "node:path";
|
|
24
24
|
function getConfigDir() {
|
|
25
25
|
return join(homedir(), ".harmony-mcp");
|
|
26
26
|
}
|
|
@@ -30,6 +30,22 @@ function getConfigPath() {
|
|
|
30
30
|
function getLocalConfigPath(cwd) {
|
|
31
31
|
return join(cwd || process.cwd(), LOCAL_CONFIG_FILENAME);
|
|
32
32
|
}
|
|
33
|
+
function findLocalConfigPath(cwd) {
|
|
34
|
+
const home = resolve(homedir());
|
|
35
|
+
let dir = resolve(cwd || process.cwd());
|
|
36
|
+
const { root } = parse(dir);
|
|
37
|
+
for (;; ) {
|
|
38
|
+
if (dir !== home && dir !== root) {
|
|
39
|
+
const candidate = join(dir, LOCAL_CONFIG_FILENAME);
|
|
40
|
+
if (existsSync(candidate))
|
|
41
|
+
return candidate;
|
|
42
|
+
}
|
|
43
|
+
const parent = dirname(dir);
|
|
44
|
+
if (parent === dir)
|
|
45
|
+
return null;
|
|
46
|
+
dir = parent;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
33
49
|
function emptyConfig() {
|
|
34
50
|
return {
|
|
35
51
|
apiKey: null,
|
|
@@ -81,8 +97,8 @@ function saveConfig(config) {
|
|
|
81
97
|
});
|
|
82
98
|
}
|
|
83
99
|
function loadLocalConfig(cwd) {
|
|
84
|
-
const localConfigPath =
|
|
85
|
-
if (
|
|
100
|
+
const localConfigPath = findLocalConfigPath(cwd);
|
|
101
|
+
if (localConfigPath === null) {
|
|
86
102
|
return null;
|
|
87
103
|
}
|
|
88
104
|
try {
|
|
@@ -97,7 +113,7 @@ function loadLocalConfig(cwd) {
|
|
|
97
113
|
}
|
|
98
114
|
}
|
|
99
115
|
function saveLocalConfig(config, cwd) {
|
|
100
|
-
const localConfigPath = getLocalConfigPath(cwd);
|
|
116
|
+
const localConfigPath = findLocalConfigPath(cwd) ?? getLocalConfigPath(cwd);
|
|
101
117
|
const existingConfig = loadLocalConfig(cwd) || {
|
|
102
118
|
workspaceId: null,
|
|
103
119
|
projectId: null
|
|
@@ -111,7 +127,7 @@ function saveLocalConfig(config, cwd) {
|
|
|
111
127
|
writeFileSync(localConfigPath, JSON.stringify(cleanConfig, null, 2));
|
|
112
128
|
}
|
|
113
129
|
function hasLocalConfig(cwd) {
|
|
114
|
-
return
|
|
130
|
+
return findLocalConfigPath(cwd) !== null;
|
|
115
131
|
}
|
|
116
132
|
function getActiveCredential() {
|
|
117
133
|
const config = loadConfig();
|
|
@@ -133,33 +149,69 @@ function getUserEmail() {
|
|
|
133
149
|
const config = loadConfig();
|
|
134
150
|
return config.userEmail;
|
|
135
151
|
}
|
|
136
|
-
function
|
|
137
|
-
if (options?.
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
152
|
+
function setActiveContext(context, options) {
|
|
153
|
+
if (options?.global) {
|
|
154
|
+
saveConfig({
|
|
155
|
+
activeWorkspaceId: context.workspaceId,
|
|
156
|
+
activeProjectId: context.projectId
|
|
157
|
+
});
|
|
158
|
+
return;
|
|
141
159
|
}
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
saveLocalConfig({ projectId }, options.cwd);
|
|
160
|
+
const localPath = findLocalConfigPath(options?.cwd);
|
|
161
|
+
if (options?.local || localPath !== null) {
|
|
162
|
+
saveLocalConfig({ workspaceId: context.workspaceId, projectId: context.projectId }, options?.cwd);
|
|
146
163
|
} else {
|
|
147
|
-
saveConfig({
|
|
164
|
+
saveConfig({
|
|
165
|
+
activeWorkspaceId: context.workspaceId,
|
|
166
|
+
activeProjectId: context.projectId
|
|
167
|
+
});
|
|
148
168
|
}
|
|
149
169
|
}
|
|
150
|
-
function
|
|
170
|
+
function setActiveWorkspace(workspaceId, options) {
|
|
171
|
+
const currentWorkspaceId = getActiveWorkspaceId(options?.cwd);
|
|
172
|
+
const keepProject = currentWorkspaceId === workspaceId;
|
|
173
|
+
setActiveContext({
|
|
174
|
+
workspaceId,
|
|
175
|
+
projectId: keepProject ? getActiveProjectId(options?.cwd) : null
|
|
176
|
+
}, options);
|
|
177
|
+
}
|
|
178
|
+
function readActiveContext(cwd) {
|
|
151
179
|
const localConfig = loadLocalConfig(cwd);
|
|
152
|
-
if (localConfig
|
|
153
|
-
return
|
|
180
|
+
if (localConfig) {
|
|
181
|
+
return {
|
|
182
|
+
workspaceId: localConfig.workspaceId ?? null,
|
|
183
|
+
projectId: localConfig.projectId ?? null
|
|
184
|
+
};
|
|
154
185
|
}
|
|
155
|
-
|
|
186
|
+
const globalConfig = loadConfig();
|
|
187
|
+
return {
|
|
188
|
+
workspaceId: globalConfig.activeWorkspaceId,
|
|
189
|
+
projectId: globalConfig.activeProjectId
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
function getActiveWorkspaceId(cwd) {
|
|
193
|
+
return readActiveContext(cwd).workspaceId;
|
|
156
194
|
}
|
|
157
195
|
function getActiveProjectId(cwd) {
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
196
|
+
return readActiveContext(cwd).projectId;
|
|
197
|
+
}
|
|
198
|
+
function getActiveContext(cwd) {
|
|
199
|
+
return describeActiveContext({
|
|
200
|
+
projectId: getActiveProjectId(cwd),
|
|
201
|
+
workspaceId: getActiveWorkspaceId(cwd)
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
function describeActiveContext(context) {
|
|
205
|
+
const { projectId, workspaceId } = context;
|
|
206
|
+
if (projectId && !workspaceId) {
|
|
207
|
+
return {
|
|
208
|
+
projectId,
|
|
209
|
+
workspaceId,
|
|
210
|
+
consistent: false,
|
|
211
|
+
note: `An active project (${projectId}) is set with no active workspace, so ` + "workspace-scoped tools cannot resolve one from it. Re-set it with " + "harmony_set_project_context, or pass workspaceId explicitly."
|
|
212
|
+
};
|
|
161
213
|
}
|
|
162
|
-
return
|
|
214
|
+
return { projectId, workspaceId, consistent: true, note: null };
|
|
163
215
|
}
|
|
164
216
|
function isConfigured() {
|
|
165
217
|
const config = loadConfig();
|
|
@@ -878,7 +930,7 @@ function lockPath() {
|
|
|
878
930
|
return join3(getConfigDir(), LOCK_FILENAME);
|
|
879
931
|
}
|
|
880
932
|
function sleep(ms) {
|
|
881
|
-
return new Promise((
|
|
933
|
+
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
882
934
|
}
|
|
883
935
|
async function withRefreshLock(fn) {
|
|
884
936
|
const path = lockPath();
|
|
@@ -1546,7 +1598,7 @@ function getRetryDelay(attempt) {
|
|
|
1546
1598
|
const delay = Math.min(RETRY_CONFIG.baseDelayMs * 2 ** attempt, RETRY_CONFIG.maxDelayMs);
|
|
1547
1599
|
return Math.round(delay + delay * 0.25 * (Math.random() * 2 - 1));
|
|
1548
1600
|
}
|
|
1549
|
-
var sleep2 = (ms) => new Promise((
|
|
1601
|
+
var sleep2 = (ms) => new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
1550
1602
|
|
|
1551
1603
|
class Semaphore {
|
|
1552
1604
|
permits;
|
|
@@ -1559,7 +1611,7 @@ class Semaphore {
|
|
|
1559
1611
|
this.permits--;
|
|
1560
1612
|
return;
|
|
1561
1613
|
}
|
|
1562
|
-
return new Promise((
|
|
1614
|
+
return new Promise((resolve2) => this.queue.push(resolve2));
|
|
1563
1615
|
}
|
|
1564
1616
|
release() {
|
|
1565
1617
|
const next = this.queue.shift();
|
|
@@ -2705,7 +2757,7 @@ async function autoExpandGraph(client3, entityId, title, content, _tags, workspa
|
|
|
2705
2757
|
});
|
|
2706
2758
|
candidates = entities.filter((e) => e.id !== entityId && (e.confidence ?? 1) >= 0.4).slice(0, maxRelations);
|
|
2707
2759
|
if (candidates.length === 0) {
|
|
2708
|
-
await new Promise((
|
|
2760
|
+
await new Promise((resolve2) => setTimeout(resolve2, 2000));
|
|
2709
2761
|
const retry = await client3.searchMemoryEntities(workspaceId, query, {
|
|
2710
2762
|
project_id: projectId,
|
|
2711
2763
|
limit: 20
|
|
@@ -3297,7 +3349,7 @@ import {
|
|
|
3297
3349
|
writeFileSync as writeFileSync3
|
|
3298
3350
|
} from "node:fs";
|
|
3299
3351
|
import { homedir as homedir3 } from "node:os";
|
|
3300
|
-
import { dirname, join as join5 } from "node:path";
|
|
3352
|
+
import { dirname as dirname2, join as join5 } from "node:path";
|
|
3301
3353
|
init_config();
|
|
3302
3354
|
|
|
3303
3355
|
// src/hmy-config.ts
|
|
@@ -3444,7 +3496,7 @@ function stripSkillPreamble(content) {
|
|
|
3444
3496
|
`;
|
|
3445
3497
|
}
|
|
3446
3498
|
function atomicWrite(filePath, content) {
|
|
3447
|
-
const dir =
|
|
3499
|
+
const dir = dirname2(filePath);
|
|
3448
3500
|
if (!existsSync4(dir))
|
|
3449
3501
|
mkdirSync3(dir, { recursive: true });
|
|
3450
3502
|
const tmp = `${filePath}.tmp-${process.pid}-${Date.now()}`;
|
|
@@ -3535,10 +3587,10 @@ async function refreshSkills(opts = {}) {
|
|
|
3535
3587
|
continue;
|
|
3536
3588
|
let siblingPath;
|
|
3537
3589
|
if (samplePath.endsWith("SKILL.md")) {
|
|
3538
|
-
const parentDir =
|
|
3590
|
+
const parentDir = dirname2(dirname2(samplePath));
|
|
3539
3591
|
siblingPath = `${parentDir}/${name}/SKILL.md`;
|
|
3540
3592
|
} else {
|
|
3541
|
-
const parentDir =
|
|
3593
|
+
const parentDir = dirname2(samplePath);
|
|
3542
3594
|
siblingPath = `${parentDir}/${name}.md`;
|
|
3543
3595
|
}
|
|
3544
3596
|
if (existsSync4(siblingPath)) {
|
|
@@ -4586,11 +4638,15 @@ var TOOLS = {
|
|
|
4586
4638
|
}
|
|
4587
4639
|
},
|
|
4588
4640
|
harmony_set_project_context: {
|
|
4589
|
-
description: "Set the active project context for subsequent operations",
|
|
4641
|
+
description: "Set the active project context for subsequent operations. The project's workspace is set with it, so the two can never point at different places; pass workspaceId to skip the lookup.",
|
|
4590
4642
|
inputSchema: {
|
|
4591
4643
|
type: "object",
|
|
4592
4644
|
properties: {
|
|
4593
|
-
projectId: { type: "string" }
|
|
4645
|
+
projectId: { type: "string" },
|
|
4646
|
+
workspaceId: {
|
|
4647
|
+
type: "string",
|
|
4648
|
+
description: "The workspace this project belongs to. Optional — looked up when omitted."
|
|
4649
|
+
}
|
|
4594
4650
|
},
|
|
4595
4651
|
required: ["projectId"]
|
|
4596
4652
|
}
|
|
@@ -4657,6 +4713,11 @@ var TOOLS = {
|
|
|
4657
4713
|
steerable: {
|
|
4658
4714
|
type: "boolean",
|
|
4659
4715
|
description: "Set true only if this session will poll harmony_get_pending_messages at its checkpoints. Enables the live steering composer for the run; leave unset/false if you won't consume steering messages."
|
|
4716
|
+
},
|
|
4717
|
+
driver: {
|
|
4718
|
+
type: "string",
|
|
4719
|
+
enum: ["daemon", "interactive", "script"],
|
|
4720
|
+
description: 'Who is calling: the agent daemon, a human-driven interactive session, or an automation script. Names you as the holder if another caller hits a 409 on this card. Defaults to "interactive".'
|
|
4660
4721
|
}
|
|
4661
4722
|
},
|
|
4662
4723
|
required: ["cardId", "agentIdentifier", "agentName"]
|
|
@@ -5389,13 +5450,26 @@ var TOOLS = {
|
|
|
5389
5450
|
type: "array",
|
|
5390
5451
|
description: "The playbook's ordered stage objects.",
|
|
5391
5452
|
items: { type: "object" }
|
|
5453
|
+
},
|
|
5454
|
+
triggerType: {
|
|
5455
|
+
type: "string",
|
|
5456
|
+
enum: ["manual", "auto"],
|
|
5457
|
+
description: "'manual' (default) — the playbook is applied by a person. 'auto' — it claims matching cards itself, and requires autoBind."
|
|
5458
|
+
},
|
|
5459
|
+
autoBind: {
|
|
5460
|
+
type: "object",
|
|
5461
|
+
description: "Auto-bind rule: {priority?: number, mode?: 'all'|'any', when: [{path, op, value}]}. Conditions are evaluated against the card's labels (lowercased), intent, complexity_score and priority with the gate operators eq/neq/gte/gt/lte/lt/contains/exists; 'contains' on labels is membership. Stored even while triggerType is 'manual', so a rule can be armed later without re-authoring it."
|
|
5462
|
+
},
|
|
5463
|
+
catalogId: {
|
|
5464
|
+
type: "string",
|
|
5465
|
+
description: "Slug of the built-in template this came from (provenance only; never used to match)."
|
|
5392
5466
|
}
|
|
5393
5467
|
},
|
|
5394
5468
|
required: ["name"]
|
|
5395
5469
|
}
|
|
5396
5470
|
},
|
|
5397
5471
|
harmony_update_playbook: {
|
|
5398
|
-
description: "Update a playbook's name, description, steps/stages, enabled flag,
|
|
5472
|
+
description: "Update a playbook's name, description, steps/stages, enabled flag, lifecycle state ('active'|'deprecated'), or its auto-bind rule and arming.",
|
|
5399
5473
|
inputSchema: {
|
|
5400
5474
|
type: "object",
|
|
5401
5475
|
properties: {
|
|
@@ -5418,6 +5492,15 @@ var TOOLS = {
|
|
|
5418
5492
|
type: "string",
|
|
5419
5493
|
enum: ["active", "deprecated"],
|
|
5420
5494
|
description: "Lifecycle state"
|
|
5495
|
+
},
|
|
5496
|
+
triggerType: {
|
|
5497
|
+
type: "string",
|
|
5498
|
+
enum: ["manual", "auto"],
|
|
5499
|
+
description: "Arm ('auto') or disarm ('manual') automatic application. Arming requires a rule to be present or supplied in the same call."
|
|
5500
|
+
},
|
|
5501
|
+
autoBind: {
|
|
5502
|
+
type: "object",
|
|
5503
|
+
description: "Replace the auto-bind rule: {priority?, mode?: 'all'|'any', when: [{path, op, value}]}. Pass null to remove it."
|
|
5421
5504
|
}
|
|
5422
5505
|
},
|
|
5423
5506
|
required: ["playbookId"]
|
|
@@ -5882,7 +5965,10 @@ async function handleToolCall(name, args, deps) {
|
|
|
5882
5965
|
const where = resolved.project.workspaceName ? `project "${resolved.project.name ?? resolved.project.id}" (workspace "${resolved.project.workspaceName}")` : `project "${resolved.project.name ?? resolved.project.id}"`;
|
|
5883
5966
|
const established = activeProjectId == null;
|
|
5884
5967
|
if (established) {
|
|
5885
|
-
deps.
|
|
5968
|
+
deps.setActiveContext({
|
|
5969
|
+
projectId: resolved.project.id,
|
|
5970
|
+
workspaceId: resolved.project.workspaceId
|
|
5971
|
+
});
|
|
5886
5972
|
}
|
|
5887
5973
|
return {
|
|
5888
5974
|
success: true,
|
|
@@ -6321,15 +6407,57 @@ ${options}
|
|
|
6321
6407
|
}
|
|
6322
6408
|
case "harmony_set_project_context": {
|
|
6323
6409
|
const projectId = z.string().uuid().parse(args.projectId);
|
|
6324
|
-
|
|
6325
|
-
|
|
6410
|
+
const explicitWorkspaceId = args.workspaceId ? z.string().uuid().parse(args.workspaceId) : null;
|
|
6411
|
+
let owningWorkspaceId = explicitWorkspaceId;
|
|
6412
|
+
if (!owningWorkspaceId) {
|
|
6413
|
+
try {
|
|
6414
|
+
const { workspaces } = await client3.listWorkspaces();
|
|
6415
|
+
for (const workspace of workspaces) {
|
|
6416
|
+
if (!workspace?.id)
|
|
6417
|
+
continue;
|
|
6418
|
+
const { projects } = await client3.listProjects(workspace.id);
|
|
6419
|
+
if (projects.some((p) => p?.id === projectId)) {
|
|
6420
|
+
owningWorkspaceId = workspace.id;
|
|
6421
|
+
break;
|
|
6422
|
+
}
|
|
6423
|
+
}
|
|
6424
|
+
} catch (error) {
|
|
6425
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
6426
|
+
return {
|
|
6427
|
+
success: false,
|
|
6428
|
+
activeProjectId: deps.getActiveProjectId(),
|
|
6429
|
+
activeWorkspaceId: deps.getActiveWorkspaceId(),
|
|
6430
|
+
note: `Could not resolve this project's workspace (${reason}), so the ` + `active context was left unchanged rather than half-written. ` + `Retry, or pass workspaceId explicitly to skip the lookup.`
|
|
6431
|
+
};
|
|
6432
|
+
}
|
|
6433
|
+
if (!owningWorkspaceId) {
|
|
6434
|
+
return {
|
|
6435
|
+
success: false,
|
|
6436
|
+
activeProjectId: deps.getActiveProjectId(),
|
|
6437
|
+
activeWorkspaceId: deps.getActiveWorkspaceId(),
|
|
6438
|
+
note: `Project ${projectId} is not in any workspace this connection ` + `can reach, so the active context was left unchanged. Check ` + `harmony_list_projects, pass workspaceId explicitly, or ` + `reconnect with /mcp if it lives in another workspace.`
|
|
6439
|
+
};
|
|
6440
|
+
}
|
|
6441
|
+
}
|
|
6442
|
+
deps.setActiveContext({ projectId, workspaceId: owningWorkspaceId });
|
|
6443
|
+
return {
|
|
6444
|
+
success: true,
|
|
6445
|
+
activeProjectId: projectId,
|
|
6446
|
+
activeWorkspaceId: owningWorkspaceId
|
|
6447
|
+
};
|
|
6326
6448
|
}
|
|
6327
6449
|
case "harmony_get_context": {
|
|
6450
|
+
const report = describeActiveContext({
|
|
6451
|
+
projectId: deps.getActiveProjectId(),
|
|
6452
|
+
workspaceId: deps.getActiveWorkspaceId()
|
|
6453
|
+
});
|
|
6328
6454
|
return {
|
|
6329
6455
|
success: true,
|
|
6330
6456
|
context: {
|
|
6331
|
-
activeWorkspaceId:
|
|
6332
|
-
activeProjectId:
|
|
6457
|
+
activeWorkspaceId: report.workspaceId,
|
|
6458
|
+
activeProjectId: report.projectId,
|
|
6459
|
+
consistent: report.consistent,
|
|
6460
|
+
...report.note ? { note: report.note } : {}
|
|
6333
6461
|
}
|
|
6334
6462
|
};
|
|
6335
6463
|
}
|
|
@@ -6411,7 +6539,8 @@ ${options}
|
|
|
6411
6539
|
status: "working",
|
|
6412
6540
|
currentTask: args.currentTask,
|
|
6413
6541
|
estimatedMinutesRemaining: optionalNonNegativeNumberArg(args.estimatedMinutesRemaining, "estimatedMinutesRemaining"),
|
|
6414
|
-
steerable: args.steerable === true || args.steerable === "true" ? true : undefined
|
|
6542
|
+
steerable: args.steerable === true || args.steerable === "true" ? true : undefined,
|
|
6543
|
+
driver: args.driver ?? "interactive"
|
|
6415
6544
|
});
|
|
6416
6545
|
markExplicit(cardId, {
|
|
6417
6546
|
agentIdentifier,
|
|
@@ -7177,7 +7306,10 @@ ${options}
|
|
|
7177
7306
|
workspaceId,
|
|
7178
7307
|
name: name2,
|
|
7179
7308
|
description: args.description,
|
|
7180
|
-
steps: args.steps
|
|
7309
|
+
steps: args.steps,
|
|
7310
|
+
triggerType: args.triggerType,
|
|
7311
|
+
autoBind: args.autoBind,
|
|
7312
|
+
catalogId: args.catalogId
|
|
7181
7313
|
});
|
|
7182
7314
|
return { success: true, playbook: result.playbook };
|
|
7183
7315
|
}
|
|
@@ -7188,7 +7320,9 @@ ${options}
|
|
|
7188
7320
|
description: args.description,
|
|
7189
7321
|
steps: args.steps,
|
|
7190
7322
|
enabled: args.enabled,
|
|
7191
|
-
state: args.state
|
|
7323
|
+
state: args.state,
|
|
7324
|
+
triggerType: args.triggerType,
|
|
7325
|
+
..."autoBind" in args ? { autoBind: args.autoBind } : {}
|
|
7192
7326
|
});
|
|
7193
7327
|
return { success: true, playbook: result.playbook };
|
|
7194
7328
|
}
|
|
@@ -7265,8 +7399,10 @@ ${options}
|
|
|
7265
7399
|
apiUrl: deps.getApiUrl()
|
|
7266
7400
|
});
|
|
7267
7401
|
deps.saveConfig({ apiKey: result.apiKey.rawKey });
|
|
7268
|
-
deps.
|
|
7269
|
-
|
|
7402
|
+
deps.setActiveContext({
|
|
7403
|
+
projectId: result.project.id,
|
|
7404
|
+
workspaceId: result.workspace.id
|
|
7405
|
+
});
|
|
7270
7406
|
deps.resetClient();
|
|
7271
7407
|
return {
|
|
7272
7408
|
success: true,
|
|
@@ -7288,7 +7424,7 @@ function createConfigDeps() {
|
|
|
7288
7424
|
isConfigured,
|
|
7289
7425
|
getActiveProjectId: () => getActiveProjectId(),
|
|
7290
7426
|
getActiveWorkspaceId: () => getActiveWorkspaceId(),
|
|
7291
|
-
|
|
7427
|
+
setActiveContext: (context) => setActiveContext(context),
|
|
7292
7428
|
setActiveWorkspace: (id) => setActiveWorkspace(id),
|
|
7293
7429
|
getApiUrl,
|
|
7294
7430
|
getMemoryDir: () => getMemoryDir(),
|
|
@@ -7367,7 +7503,7 @@ import {
|
|
|
7367
7503
|
unlinkSync
|
|
7368
7504
|
} from "node:fs";
|
|
7369
7505
|
import { homedir as homedir6 } from "node:os";
|
|
7370
|
-
import { dirname as
|
|
7506
|
+
import { dirname as dirname4, join as join8 } from "node:path";
|
|
7371
7507
|
import * as p4 from "@clack/prompts";
|
|
7372
7508
|
init_config();
|
|
7373
7509
|
init_oauth_login();
|
|
@@ -7439,7 +7575,7 @@ async function confirmOrDefault(assumeYes, opts) {
|
|
|
7439
7575
|
|
|
7440
7576
|
// src/tui/docs.ts
|
|
7441
7577
|
import { existsSync as existsSync6, readdirSync as readdirSync2, readFileSync as readFileSync5, statSync as statSync2 } from "node:fs";
|
|
7442
|
-
import { isAbsolute, join as join7, resolve, sep as sep2 } from "node:path";
|
|
7578
|
+
import { isAbsolute, join as join7, resolve as resolve2, sep as sep2 } from "node:path";
|
|
7443
7579
|
import * as p2 from "@clack/prompts";
|
|
7444
7580
|
|
|
7445
7581
|
// src/tui/theme.ts
|
|
@@ -7863,7 +7999,7 @@ function verifyDocs(cwd) {
|
|
|
7863
7999
|
const agentsMd = readText(join7(cwd, "AGENTS.md"));
|
|
7864
8000
|
const pkg = readJson(join7(cwd, "package.json"));
|
|
7865
8001
|
const pkgScripts = pkg && typeof pkg.scripts === "object" && pkg.scripts !== null ? pkg.scripts : {};
|
|
7866
|
-
const projectRoot =
|
|
8002
|
+
const projectRoot = resolve2(cwd);
|
|
7867
8003
|
if (claudeMd) {
|
|
7868
8004
|
const importedFiles = [];
|
|
7869
8005
|
for (const line of claudeMd.split(`
|
|
@@ -7880,7 +8016,7 @@ function verifyDocs(cwd) {
|
|
|
7880
8016
|
});
|
|
7881
8017
|
continue;
|
|
7882
8018
|
}
|
|
7883
|
-
const resolvedPath =
|
|
8019
|
+
const resolvedPath = resolve2(projectRoot, refPath);
|
|
7884
8020
|
if (resolvedPath !== projectRoot && !resolvedPath.startsWith(projectRoot + sep2)) {
|
|
7885
8021
|
issues.push({
|
|
7886
8022
|
severity: "error",
|
|
@@ -8065,13 +8201,13 @@ function checkBacktickPaths(content, file, cwd, issues) {
|
|
|
8065
8201
|
const pathRe = /`((?:src\/|packages\/|apps\/|supabase\/|docs\/)[^`]+)`/g;
|
|
8066
8202
|
let match;
|
|
8067
8203
|
const checked = new Set;
|
|
8068
|
-
const root =
|
|
8204
|
+
const root = resolve2(cwd);
|
|
8069
8205
|
while ((match = pathRe.exec(content)) !== null) {
|
|
8070
8206
|
const refPath = match[1].replace(/\/$/, "");
|
|
8071
8207
|
if (checked.has(refPath))
|
|
8072
8208
|
continue;
|
|
8073
8209
|
checked.add(refPath);
|
|
8074
|
-
const resolvedRef =
|
|
8210
|
+
const resolvedRef = resolve2(root, refPath);
|
|
8075
8211
|
if (resolvedRef !== root && !resolvedRef.startsWith(root + sep2))
|
|
8076
8212
|
continue;
|
|
8077
8213
|
if (!existsSync6(resolvedRef)) {
|
|
@@ -8152,7 +8288,7 @@ import {
|
|
|
8152
8288
|
writeFileSync as writeFileSync4
|
|
8153
8289
|
} from "node:fs";
|
|
8154
8290
|
import { homedir as homedir5 } from "node:os";
|
|
8155
|
-
import { dirname as
|
|
8291
|
+
import { dirname as dirname3 } from "node:path";
|
|
8156
8292
|
import * as p3 from "@clack/prompts";
|
|
8157
8293
|
function ensureDir(dirPath) {
|
|
8158
8294
|
if (!existsSync7(dirPath)) {
|
|
@@ -8165,7 +8301,7 @@ function writeFile(filePath, content, options = {}) {
|
|
|
8165
8301
|
return { path: filePath, action: "skip" };
|
|
8166
8302
|
}
|
|
8167
8303
|
try {
|
|
8168
|
-
ensureDir(
|
|
8304
|
+
ensureDir(dirname3(filePath));
|
|
8169
8305
|
const defaultMode = filePath.includes(".harmony-mcp") ? 384 : 420;
|
|
8170
8306
|
const mode = options.mode ?? defaultMode;
|
|
8171
8307
|
writeFileSync4(filePath, content, { mode });
|
|
@@ -8185,7 +8321,7 @@ function mergeJsonFile(filePath, updates, options = {}) {
|
|
|
8185
8321
|
const exists = existsSync7(filePath);
|
|
8186
8322
|
if (!exists) {
|
|
8187
8323
|
try {
|
|
8188
|
-
ensureDir(
|
|
8324
|
+
ensureDir(dirname3(filePath));
|
|
8189
8325
|
writeFileSync4(filePath, JSON.stringify(updates, null, 2), {
|
|
8190
8326
|
mode: 420
|
|
8191
8327
|
});
|
|
@@ -8235,7 +8371,7 @@ function appendToToml(filePath, section, content, options = {}) {
|
|
|
8235
8371
|
const exists = existsSync7(filePath);
|
|
8236
8372
|
if (!exists) {
|
|
8237
8373
|
try {
|
|
8238
|
-
ensureDir(
|
|
8374
|
+
ensureDir(dirname3(filePath));
|
|
8239
8375
|
writeFileSync4(filePath, content, { mode: 420 });
|
|
8240
8376
|
return { path: filePath, action: "create" };
|
|
8241
8377
|
} catch (error) {
|
|
@@ -8289,7 +8425,7 @@ async function writeFilesWithProgress(files, options = {}) {
|
|
|
8289
8425
|
});
|
|
8290
8426
|
}
|
|
8291
8427
|
results.push(result);
|
|
8292
|
-
await new Promise((
|
|
8428
|
+
await new Promise((resolve3) => setTimeout(resolve3, 50));
|
|
8293
8429
|
}
|
|
8294
8430
|
spinner2.stop("Files written");
|
|
8295
8431
|
for (const result of results) {
|
|
@@ -8389,7 +8525,7 @@ async function registerMcpServer() {
|
|
|
8389
8525
|
async function writeMcpConfigFallback(home) {
|
|
8390
8526
|
const { readFileSync: readFileSync7, writeFileSync: writeFileSync5, mkdirSync: mkdirSync6, existsSync: existsSync9 } = await import("node:fs");
|
|
8391
8527
|
const settingsPath = join8(home, ".claude", "settings.json");
|
|
8392
|
-
const settingsDir =
|
|
8528
|
+
const settingsDir = dirname4(settingsPath);
|
|
8393
8529
|
if (!existsSync9(settingsDir)) {
|
|
8394
8530
|
mkdirSync6(settingsDir, { recursive: true });
|
|
8395
8531
|
}
|
|
@@ -8408,7 +8544,7 @@ async function writeMcpConfigFallback(home) {
|
|
|
8408
8544
|
async function allowlistHarmonyTools(home, allowAll) {
|
|
8409
8545
|
const { readFileSync: readFileSync7, writeFileSync: writeFileSync5, mkdirSync: mkdirSync6, existsSync: existsSync9 } = await import("node:fs");
|
|
8410
8546
|
const settingsPath = join8(home, ".claude", "settings.json");
|
|
8411
|
-
const settingsDir =
|
|
8547
|
+
const settingsDir = dirname4(settingsPath);
|
|
8412
8548
|
if (!existsSync9(settingsDir)) {
|
|
8413
8549
|
mkdirSync6(settingsDir, { recursive: true });
|
|
8414
8550
|
}
|
|
@@ -8940,8 +9076,10 @@ ${colors.dim(url)}`);
|
|
|
8940
9076
|
createdNewAccount = true;
|
|
8941
9077
|
needsApiKey = true;
|
|
8942
9078
|
saveConfig({ apiKey, userEmail, apiUrl: API_URL });
|
|
8943
|
-
|
|
8944
|
-
|
|
9079
|
+
setActiveContext({
|
|
9080
|
+
workspaceId: selectedWorkspaceIdFromSignup,
|
|
9081
|
+
projectId: selectedProjectIdFromSignup
|
|
9082
|
+
});
|
|
8945
9083
|
p4.log.success("Workspace and board created");
|
|
8946
9084
|
} catch (error) {
|
|
8947
9085
|
spinner4.stop(colors.error("Account creation failed"));
|
|
@@ -9273,7 +9411,7 @@ Specify the workspace with --workspace <id>, or select one below.`);
|
|
|
9273
9411
|
if (allSymlinks.length > 0) {
|
|
9274
9412
|
for (const symlink of allSymlinks) {
|
|
9275
9413
|
try {
|
|
9276
|
-
const linkDir =
|
|
9414
|
+
const linkDir = dirname4(symlink.link);
|
|
9277
9415
|
if (!existsSync8(linkDir)) {
|
|
9278
9416
|
mkdirSync5(linkDir, { recursive: true });
|
|
9279
9417
|
}
|
|
@@ -9340,10 +9478,12 @@ Specify the workspace with --workspace <id>, or select one below.`);
|
|
|
9340
9478
|
localConfig.projectId = selectedProjectId;
|
|
9341
9479
|
saveLocalConfig(localConfig, cwd);
|
|
9342
9480
|
console.log(` ${colors.success("✓")} ${colors.dim(formatPath(getLocalConfigPath(cwd), home))} ${colors.dim("(created)")}`);
|
|
9343
|
-
if (selectedWorkspaceId)
|
|
9344
|
-
|
|
9345
|
-
|
|
9346
|
-
|
|
9481
|
+
if (selectedWorkspaceId || selectedProjectId) {
|
|
9482
|
+
setActiveContext({
|
|
9483
|
+
workspaceId: selectedWorkspaceId ?? null,
|
|
9484
|
+
projectId: selectedProjectId ?? null
|
|
9485
|
+
}, { global: true });
|
|
9486
|
+
}
|
|
9347
9487
|
}
|
|
9348
9488
|
console.log("");
|
|
9349
9489
|
p4.outro(colors.success("Setup complete!"));
|
|
@@ -9395,7 +9535,7 @@ Specify the workspace with --workspace <id>, or select one below.`);
|
|
|
9395
9535
|
// src/cli.ts
|
|
9396
9536
|
var require2 = createRequire2(import.meta.url);
|
|
9397
9537
|
var { version } = require2("../package.json");
|
|
9398
|
-
program.name("@gethmy/mcp").description("MCP server for Harmony
|
|
9538
|
+
program.name("@gethmy/mcp").description("MCP server for Harmony — the shared surface for human–agent teams").version(version);
|
|
9399
9539
|
program.command("serve").description("Start the MCP server (stdio transport)").action(async () => {
|
|
9400
9540
|
if (!isConfigured()) {
|
|
9401
9541
|
console.error("No API key configured.");
|
|
@@ -9442,7 +9582,7 @@ Skills:`);
|
|
|
9442
9582
|
console.log(`
|
|
9443
9583
|
Context:`);
|
|
9444
9584
|
if (hasLocal) {
|
|
9445
|
-
console.log(` Local config: ${
|
|
9585
|
+
console.log(` Local config: ${findLocalConfigPath()}`);
|
|
9446
9586
|
console.log(` Workspace: ${localConfig?.workspaceId || "(not set)"}`);
|
|
9447
9587
|
console.log(` Project: ${localConfig?.projectId || "(not set)"}`);
|
|
9448
9588
|
}
|
|
@@ -9451,12 +9591,18 @@ Context:`);
|
|
|
9451
9591
|
console.log(` Project: ${globalConfig.activeProjectId || "(not set)"}`);
|
|
9452
9592
|
const effectiveWorkspace = getActiveWorkspaceId();
|
|
9453
9593
|
const effectiveProject = getActiveProjectId();
|
|
9454
|
-
const
|
|
9455
|
-
const
|
|
9594
|
+
const contextSource = hasLocal ? "local" : "global";
|
|
9595
|
+
const wsSource = effectiveWorkspace ? contextSource : "";
|
|
9596
|
+
const projSource = effectiveProject ? contextSource : "";
|
|
9456
9597
|
console.log(`
|
|
9457
9598
|
Active (effective):`);
|
|
9458
9599
|
console.log(` Workspace: ${effectiveWorkspace || "(not set)"}${wsSource ? ` ← ${wsSource}` : ""}`);
|
|
9459
9600
|
console.log(` Project: ${effectiveProject || "(not set)"}${projSource ? ` ← ${projSource}` : ""}`);
|
|
9601
|
+
const report = getActiveContext();
|
|
9602
|
+
if (!report.consistent && report.note) {
|
|
9603
|
+
console.log(`
|
|
9604
|
+
⚠ ${report.note}`);
|
|
9605
|
+
}
|
|
9460
9606
|
} else {
|
|
9461
9607
|
console.log(`Status: Not configured
|
|
9462
9608
|
`);
|
|
@@ -9479,7 +9625,7 @@ program.command("reset").description("Remove stored configuration").action(() =>
|
|
|
9479
9625
|
console.log(`
|
|
9480
9626
|
To reconfigure, run: npx @gethmy/mcp setup`);
|
|
9481
9627
|
});
|
|
9482
|
-
program.command("setup").description("
|
|
9628
|
+
program.command("setup").description("Setup wizard for Harmony MCP (recommended)").argument("[slug]", "Project slug — resolves to workspace + project in one step (e.g. harmony-6590761b)").option("-f, --force", "Overwrite existing configuration files").option("-k, --api-key <key>", "DEPRECATED (insecure: key leaks via argv/shell history). For unattended CI only — interactive setup uses browser sign-in.").option("-e, --email <email>", "Your email for auto-assignment").option("-a, --agents <agents...>", "Agents to configure: claude, codex, cursor, windsurf").option("-l, --local", "Install skills locally in project directory").option("-g, --global", "Install skills globally (recommended)").option("-w, --workspace <id>", "Set workspace context (UUID)").option("-p, --project <id>", "Set project context (UUID)").option("--skip-context", "Skip workspace/project selection").option("--skip-docs", "Skip project docs scaffold/verification").option("-y, --yes", "Non-interactive: answer every yes/no confirmation with its default. Implied when there is no TTY (pipe / coding agent / CI). Provide the other inputs via flags (--api-key, --agents, --workspace/--project or --skip-context, --skip-docs).").option("--new", "Create a new account (skip the choice prompt)").option("-n, --name <name>", "Full name (for account creation)").option("--allow-all-tools", "Allowlist every Harmony tool (incl. destructive: delete/archive/api-key/invite) without confirmation. Default allowlists only read + routine-write tools; destructive tools keep prompting.").action(async (slug, options) => {
|
|
9483
9629
|
await runSetup({
|
|
9484
9630
|
force: options.force,
|
|
9485
9631
|
apiKey: options.apiKey,
|