@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/index.js
CHANGED
|
@@ -20,7 +20,7 @@ var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
|
20
20
|
// src/config.ts
|
|
21
21
|
import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
|
22
22
|
import { homedir } from "node:os";
|
|
23
|
-
import { join as join2 } from "node:path";
|
|
23
|
+
import { dirname, join as join2, parse, resolve } from "node:path";
|
|
24
24
|
function getConfigDir() {
|
|
25
25
|
return join2(homedir(), ".harmony-mcp");
|
|
26
26
|
}
|
|
@@ -30,6 +30,22 @@ function getConfigPath() {
|
|
|
30
30
|
function getLocalConfigPath(cwd) {
|
|
31
31
|
return join2(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 = join2(dir, LOCAL_CONFIG_FILENAME);
|
|
40
|
+
if (existsSync2(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
|
writeFileSync2(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();
|
|
@@ -1541,7 +1593,7 @@ function getRetryDelay(attempt) {
|
|
|
1541
1593
|
const delay = Math.min(RETRY_CONFIG.baseDelayMs * 2 ** attempt, RETRY_CONFIG.maxDelayMs);
|
|
1542
1594
|
return Math.round(delay + delay * 0.25 * (Math.random() * 2 - 1));
|
|
1543
1595
|
}
|
|
1544
|
-
var sleep2 = (ms) => new Promise((
|
|
1596
|
+
var sleep2 = (ms) => new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
1545
1597
|
|
|
1546
1598
|
class Semaphore {
|
|
1547
1599
|
permits;
|
|
@@ -1554,7 +1606,7 @@ class Semaphore {
|
|
|
1554
1606
|
this.permits--;
|
|
1555
1607
|
return;
|
|
1556
1608
|
}
|
|
1557
|
-
return new Promise((
|
|
1609
|
+
return new Promise((resolve2) => this.queue.push(resolve2));
|
|
1558
1610
|
}
|
|
1559
1611
|
release() {
|
|
1560
1612
|
const next = this.queue.shift();
|
|
@@ -2700,7 +2752,7 @@ async function autoExpandGraph(client3, entityId, title, content, _tags, workspa
|
|
|
2700
2752
|
});
|
|
2701
2753
|
candidates = entities.filter((e) => e.id !== entityId && (e.confidence ?? 1) >= 0.4).slice(0, maxRelations);
|
|
2702
2754
|
if (candidates.length === 0) {
|
|
2703
|
-
await new Promise((
|
|
2755
|
+
await new Promise((resolve2) => setTimeout(resolve2, 2000));
|
|
2704
2756
|
const retry = await client3.searchMemoryEntities(workspaceId, query, {
|
|
2705
2757
|
project_id: projectId,
|
|
2706
2758
|
limit: 20
|
|
@@ -3292,7 +3344,7 @@ import {
|
|
|
3292
3344
|
writeFileSync as writeFileSync3
|
|
3293
3345
|
} from "node:fs";
|
|
3294
3346
|
import { homedir as homedir3 } from "node:os";
|
|
3295
|
-
import { dirname, join as join5 } from "node:path";
|
|
3347
|
+
import { dirname as dirname2, join as join5 } from "node:path";
|
|
3296
3348
|
init_config();
|
|
3297
3349
|
|
|
3298
3350
|
// src/hmy-config.ts
|
|
@@ -3439,7 +3491,7 @@ function stripSkillPreamble(content) {
|
|
|
3439
3491
|
`;
|
|
3440
3492
|
}
|
|
3441
3493
|
function atomicWrite(filePath, content) {
|
|
3442
|
-
const dir =
|
|
3494
|
+
const dir = dirname2(filePath);
|
|
3443
3495
|
if (!existsSync4(dir))
|
|
3444
3496
|
mkdirSync3(dir, { recursive: true });
|
|
3445
3497
|
const tmp = `${filePath}.tmp-${process.pid}-${Date.now()}`;
|
|
@@ -3530,10 +3582,10 @@ async function refreshSkills(opts = {}) {
|
|
|
3530
3582
|
continue;
|
|
3531
3583
|
let siblingPath;
|
|
3532
3584
|
if (samplePath.endsWith("SKILL.md")) {
|
|
3533
|
-
const parentDir =
|
|
3585
|
+
const parentDir = dirname2(dirname2(samplePath));
|
|
3534
3586
|
siblingPath = `${parentDir}/${name}/SKILL.md`;
|
|
3535
3587
|
} else {
|
|
3536
|
-
const parentDir =
|
|
3588
|
+
const parentDir = dirname2(samplePath);
|
|
3537
3589
|
siblingPath = `${parentDir}/${name}.md`;
|
|
3538
3590
|
}
|
|
3539
3591
|
if (existsSync4(siblingPath)) {
|
|
@@ -4581,11 +4633,15 @@ var TOOLS = {
|
|
|
4581
4633
|
}
|
|
4582
4634
|
},
|
|
4583
4635
|
harmony_set_project_context: {
|
|
4584
|
-
description: "Set the active project context for subsequent operations",
|
|
4636
|
+
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.",
|
|
4585
4637
|
inputSchema: {
|
|
4586
4638
|
type: "object",
|
|
4587
4639
|
properties: {
|
|
4588
|
-
projectId: { type: "string" }
|
|
4640
|
+
projectId: { type: "string" },
|
|
4641
|
+
workspaceId: {
|
|
4642
|
+
type: "string",
|
|
4643
|
+
description: "The workspace this project belongs to. Optional — looked up when omitted."
|
|
4644
|
+
}
|
|
4589
4645
|
},
|
|
4590
4646
|
required: ["projectId"]
|
|
4591
4647
|
}
|
|
@@ -4652,6 +4708,11 @@ var TOOLS = {
|
|
|
4652
4708
|
steerable: {
|
|
4653
4709
|
type: "boolean",
|
|
4654
4710
|
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."
|
|
4711
|
+
},
|
|
4712
|
+
driver: {
|
|
4713
|
+
type: "string",
|
|
4714
|
+
enum: ["daemon", "interactive", "script"],
|
|
4715
|
+
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".'
|
|
4655
4716
|
}
|
|
4656
4717
|
},
|
|
4657
4718
|
required: ["cardId", "agentIdentifier", "agentName"]
|
|
@@ -5384,13 +5445,26 @@ var TOOLS = {
|
|
|
5384
5445
|
type: "array",
|
|
5385
5446
|
description: "The playbook's ordered stage objects.",
|
|
5386
5447
|
items: { type: "object" }
|
|
5448
|
+
},
|
|
5449
|
+
triggerType: {
|
|
5450
|
+
type: "string",
|
|
5451
|
+
enum: ["manual", "auto"],
|
|
5452
|
+
description: "'manual' (default) — the playbook is applied by a person. 'auto' — it claims matching cards itself, and requires autoBind."
|
|
5453
|
+
},
|
|
5454
|
+
autoBind: {
|
|
5455
|
+
type: "object",
|
|
5456
|
+
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."
|
|
5457
|
+
},
|
|
5458
|
+
catalogId: {
|
|
5459
|
+
type: "string",
|
|
5460
|
+
description: "Slug of the built-in template this came from (provenance only; never used to match)."
|
|
5387
5461
|
}
|
|
5388
5462
|
},
|
|
5389
5463
|
required: ["name"]
|
|
5390
5464
|
}
|
|
5391
5465
|
},
|
|
5392
5466
|
harmony_update_playbook: {
|
|
5393
|
-
description: "Update a playbook's name, description, steps/stages, enabled flag,
|
|
5467
|
+
description: "Update a playbook's name, description, steps/stages, enabled flag, lifecycle state ('active'|'deprecated'), or its auto-bind rule and arming.",
|
|
5394
5468
|
inputSchema: {
|
|
5395
5469
|
type: "object",
|
|
5396
5470
|
properties: {
|
|
@@ -5413,6 +5487,15 @@ var TOOLS = {
|
|
|
5413
5487
|
type: "string",
|
|
5414
5488
|
enum: ["active", "deprecated"],
|
|
5415
5489
|
description: "Lifecycle state"
|
|
5490
|
+
},
|
|
5491
|
+
triggerType: {
|
|
5492
|
+
type: "string",
|
|
5493
|
+
enum: ["manual", "auto"],
|
|
5494
|
+
description: "Arm ('auto') or disarm ('manual') automatic application. Arming requires a rule to be present or supplied in the same call."
|
|
5495
|
+
},
|
|
5496
|
+
autoBind: {
|
|
5497
|
+
type: "object",
|
|
5498
|
+
description: "Replace the auto-bind rule: {priority?, mode?: 'all'|'any', when: [{path, op, value}]}. Pass null to remove it."
|
|
5416
5499
|
}
|
|
5417
5500
|
},
|
|
5418
5501
|
required: ["playbookId"]
|
|
@@ -5877,7 +5960,10 @@ async function handleToolCall(name, args, deps) {
|
|
|
5877
5960
|
const where = resolved.project.workspaceName ? `project "${resolved.project.name ?? resolved.project.id}" (workspace "${resolved.project.workspaceName}")` : `project "${resolved.project.name ?? resolved.project.id}"`;
|
|
5878
5961
|
const established = activeProjectId == null;
|
|
5879
5962
|
if (established) {
|
|
5880
|
-
deps.
|
|
5963
|
+
deps.setActiveContext({
|
|
5964
|
+
projectId: resolved.project.id,
|
|
5965
|
+
workspaceId: resolved.project.workspaceId
|
|
5966
|
+
});
|
|
5881
5967
|
}
|
|
5882
5968
|
return {
|
|
5883
5969
|
success: true,
|
|
@@ -6316,15 +6402,57 @@ ${options}
|
|
|
6316
6402
|
}
|
|
6317
6403
|
case "harmony_set_project_context": {
|
|
6318
6404
|
const projectId = z.string().uuid().parse(args.projectId);
|
|
6319
|
-
|
|
6320
|
-
|
|
6405
|
+
const explicitWorkspaceId = args.workspaceId ? z.string().uuid().parse(args.workspaceId) : null;
|
|
6406
|
+
let owningWorkspaceId = explicitWorkspaceId;
|
|
6407
|
+
if (!owningWorkspaceId) {
|
|
6408
|
+
try {
|
|
6409
|
+
const { workspaces } = await client3.listWorkspaces();
|
|
6410
|
+
for (const workspace of workspaces) {
|
|
6411
|
+
if (!workspace?.id)
|
|
6412
|
+
continue;
|
|
6413
|
+
const { projects } = await client3.listProjects(workspace.id);
|
|
6414
|
+
if (projects.some((p) => p?.id === projectId)) {
|
|
6415
|
+
owningWorkspaceId = workspace.id;
|
|
6416
|
+
break;
|
|
6417
|
+
}
|
|
6418
|
+
}
|
|
6419
|
+
} catch (error) {
|
|
6420
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
6421
|
+
return {
|
|
6422
|
+
success: false,
|
|
6423
|
+
activeProjectId: deps.getActiveProjectId(),
|
|
6424
|
+
activeWorkspaceId: deps.getActiveWorkspaceId(),
|
|
6425
|
+
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.`
|
|
6426
|
+
};
|
|
6427
|
+
}
|
|
6428
|
+
if (!owningWorkspaceId) {
|
|
6429
|
+
return {
|
|
6430
|
+
success: false,
|
|
6431
|
+
activeProjectId: deps.getActiveProjectId(),
|
|
6432
|
+
activeWorkspaceId: deps.getActiveWorkspaceId(),
|
|
6433
|
+
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.`
|
|
6434
|
+
};
|
|
6435
|
+
}
|
|
6436
|
+
}
|
|
6437
|
+
deps.setActiveContext({ projectId, workspaceId: owningWorkspaceId });
|
|
6438
|
+
return {
|
|
6439
|
+
success: true,
|
|
6440
|
+
activeProjectId: projectId,
|
|
6441
|
+
activeWorkspaceId: owningWorkspaceId
|
|
6442
|
+
};
|
|
6321
6443
|
}
|
|
6322
6444
|
case "harmony_get_context": {
|
|
6445
|
+
const report = describeActiveContext({
|
|
6446
|
+
projectId: deps.getActiveProjectId(),
|
|
6447
|
+
workspaceId: deps.getActiveWorkspaceId()
|
|
6448
|
+
});
|
|
6323
6449
|
return {
|
|
6324
6450
|
success: true,
|
|
6325
6451
|
context: {
|
|
6326
|
-
activeWorkspaceId:
|
|
6327
|
-
activeProjectId:
|
|
6452
|
+
activeWorkspaceId: report.workspaceId,
|
|
6453
|
+
activeProjectId: report.projectId,
|
|
6454
|
+
consistent: report.consistent,
|
|
6455
|
+
...report.note ? { note: report.note } : {}
|
|
6328
6456
|
}
|
|
6329
6457
|
};
|
|
6330
6458
|
}
|
|
@@ -6406,7 +6534,8 @@ ${options}
|
|
|
6406
6534
|
status: "working",
|
|
6407
6535
|
currentTask: args.currentTask,
|
|
6408
6536
|
estimatedMinutesRemaining: optionalNonNegativeNumberArg(args.estimatedMinutesRemaining, "estimatedMinutesRemaining"),
|
|
6409
|
-
steerable: args.steerable === true || args.steerable === "true" ? true : undefined
|
|
6537
|
+
steerable: args.steerable === true || args.steerable === "true" ? true : undefined,
|
|
6538
|
+
driver: args.driver ?? "interactive"
|
|
6410
6539
|
});
|
|
6411
6540
|
markExplicit(cardId, {
|
|
6412
6541
|
agentIdentifier,
|
|
@@ -7172,7 +7301,10 @@ ${options}
|
|
|
7172
7301
|
workspaceId,
|
|
7173
7302
|
name: name2,
|
|
7174
7303
|
description: args.description,
|
|
7175
|
-
steps: args.steps
|
|
7304
|
+
steps: args.steps,
|
|
7305
|
+
triggerType: args.triggerType,
|
|
7306
|
+
autoBind: args.autoBind,
|
|
7307
|
+
catalogId: args.catalogId
|
|
7176
7308
|
});
|
|
7177
7309
|
return { success: true, playbook: result.playbook };
|
|
7178
7310
|
}
|
|
@@ -7183,7 +7315,9 @@ ${options}
|
|
|
7183
7315
|
description: args.description,
|
|
7184
7316
|
steps: args.steps,
|
|
7185
7317
|
enabled: args.enabled,
|
|
7186
|
-
state: args.state
|
|
7318
|
+
state: args.state,
|
|
7319
|
+
triggerType: args.triggerType,
|
|
7320
|
+
..."autoBind" in args ? { autoBind: args.autoBind } : {}
|
|
7187
7321
|
});
|
|
7188
7322
|
return { success: true, playbook: result.playbook };
|
|
7189
7323
|
}
|
|
@@ -7260,8 +7394,10 @@ ${options}
|
|
|
7260
7394
|
apiUrl: deps.getApiUrl()
|
|
7261
7395
|
});
|
|
7262
7396
|
deps.saveConfig({ apiKey: result.apiKey.rawKey });
|
|
7263
|
-
deps.
|
|
7264
|
-
|
|
7397
|
+
deps.setActiveContext({
|
|
7398
|
+
projectId: result.project.id,
|
|
7399
|
+
workspaceId: result.workspace.id
|
|
7400
|
+
});
|
|
7265
7401
|
deps.resetClient();
|
|
7266
7402
|
return {
|
|
7267
7403
|
success: true,
|
|
@@ -7283,7 +7419,7 @@ function createConfigDeps() {
|
|
|
7283
7419
|
isConfigured,
|
|
7284
7420
|
getActiveProjectId: () => getActiveProjectId(),
|
|
7285
7421
|
getActiveWorkspaceId: () => getActiveWorkspaceId(),
|
|
7286
|
-
|
|
7422
|
+
setActiveContext: (context) => setActiveContext(context),
|
|
7287
7423
|
setActiveWorkspace: (id) => setActiveWorkspace(id),
|
|
7288
7424
|
getApiUrl,
|
|
7289
7425
|
getMemoryDir: () => getMemoryDir(),
|
package/dist/lib/api-client.js
CHANGED
|
@@ -17,7 +17,7 @@ var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
|
17
17
|
// src/config.ts
|
|
18
18
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
19
19
|
import { homedir } from "node:os";
|
|
20
|
-
import { join } from "node:path";
|
|
20
|
+
import { dirname, join, parse, resolve } from "node:path";
|
|
21
21
|
function getConfigDir() {
|
|
22
22
|
return join(homedir(), ".harmony-mcp");
|
|
23
23
|
}
|
|
@@ -27,6 +27,22 @@ function getConfigPath() {
|
|
|
27
27
|
function getLocalConfigPath(cwd) {
|
|
28
28
|
return join(cwd || process.cwd(), LOCAL_CONFIG_FILENAME);
|
|
29
29
|
}
|
|
30
|
+
function findLocalConfigPath(cwd) {
|
|
31
|
+
const home = resolve(homedir());
|
|
32
|
+
let dir = resolve(cwd || process.cwd());
|
|
33
|
+
const { root } = parse(dir);
|
|
34
|
+
for (;; ) {
|
|
35
|
+
if (dir !== home && dir !== root) {
|
|
36
|
+
const candidate = join(dir, LOCAL_CONFIG_FILENAME);
|
|
37
|
+
if (existsSync(candidate))
|
|
38
|
+
return candidate;
|
|
39
|
+
}
|
|
40
|
+
const parent = dirname(dir);
|
|
41
|
+
if (parent === dir)
|
|
42
|
+
return null;
|
|
43
|
+
dir = parent;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
30
46
|
function emptyConfig() {
|
|
31
47
|
return {
|
|
32
48
|
apiKey: null,
|
|
@@ -78,8 +94,8 @@ function saveConfig(config) {
|
|
|
78
94
|
});
|
|
79
95
|
}
|
|
80
96
|
function loadLocalConfig(cwd) {
|
|
81
|
-
const localConfigPath =
|
|
82
|
-
if (
|
|
97
|
+
const localConfigPath = findLocalConfigPath(cwd);
|
|
98
|
+
if (localConfigPath === null) {
|
|
83
99
|
return null;
|
|
84
100
|
}
|
|
85
101
|
try {
|
|
@@ -94,7 +110,7 @@ function loadLocalConfig(cwd) {
|
|
|
94
110
|
}
|
|
95
111
|
}
|
|
96
112
|
function saveLocalConfig(config, cwd) {
|
|
97
|
-
const localConfigPath = getLocalConfigPath(cwd);
|
|
113
|
+
const localConfigPath = findLocalConfigPath(cwd) ?? getLocalConfigPath(cwd);
|
|
98
114
|
const existingConfig = loadLocalConfig(cwd) || {
|
|
99
115
|
workspaceId: null,
|
|
100
116
|
projectId: null
|
|
@@ -108,7 +124,7 @@ function saveLocalConfig(config, cwd) {
|
|
|
108
124
|
writeFileSync(localConfigPath, JSON.stringify(cleanConfig, null, 2));
|
|
109
125
|
}
|
|
110
126
|
function hasLocalConfig(cwd) {
|
|
111
|
-
return
|
|
127
|
+
return findLocalConfigPath(cwd) !== null;
|
|
112
128
|
}
|
|
113
129
|
function getActiveCredential() {
|
|
114
130
|
const config = loadConfig();
|
|
@@ -133,33 +149,69 @@ function getUserEmail() {
|
|
|
133
149
|
function setUserEmail(email) {
|
|
134
150
|
saveConfig({ userEmail: email });
|
|
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();
|
|
@@ -238,7 +290,7 @@ function lockPath() {
|
|
|
238
290
|
return join2(getConfigDir(), LOCK_FILENAME);
|
|
239
291
|
}
|
|
240
292
|
function sleep(ms) {
|
|
241
|
-
return new Promise((
|
|
293
|
+
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
242
294
|
}
|
|
243
295
|
async function withRefreshLock(fn) {
|
|
244
296
|
const path = lockPath();
|
|
@@ -993,7 +1045,7 @@ function getRetryDelay(attempt) {
|
|
|
993
1045
|
const delay = Math.min(RETRY_CONFIG.baseDelayMs * 2 ** attempt, RETRY_CONFIG.maxDelayMs);
|
|
994
1046
|
return Math.round(delay + delay * 0.25 * (Math.random() * 2 - 1));
|
|
995
1047
|
}
|
|
996
|
-
var sleep2 = (ms) => new Promise((
|
|
1048
|
+
var sleep2 = (ms) => new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
997
1049
|
|
|
998
1050
|
class Semaphore {
|
|
999
1051
|
permits;
|
|
@@ -1006,7 +1058,7 @@ class Semaphore {
|
|
|
1006
1058
|
this.permits--;
|
|
1007
1059
|
return;
|
|
1008
1060
|
}
|
|
1009
|
-
return new Promise((
|
|
1061
|
+
return new Promise((resolve2) => this.queue.push(resolve2));
|
|
1010
1062
|
}
|
|
1011
1063
|
release() {
|
|
1012
1064
|
const next = this.queue.shift();
|