@gethmy/mcp 2.23.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/dist/cli.js +207 -67
- package/dist/index.js +174 -44
- 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 +1 -1
- package/src/api-client.ts +6 -0
- package/src/cli.ts +21 -12
- package/src/config.ts +201 -27
- package/src/remote.ts +34 -6
- package/src/server.ts +154 -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
|
}
|
|
@@ -5389,13 +5445,26 @@ var TOOLS = {
|
|
|
5389
5445
|
type: "array",
|
|
5390
5446
|
description: "The playbook's ordered stage objects.",
|
|
5391
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)."
|
|
5392
5461
|
}
|
|
5393
5462
|
},
|
|
5394
5463
|
required: ["name"]
|
|
5395
5464
|
}
|
|
5396
5465
|
},
|
|
5397
5466
|
harmony_update_playbook: {
|
|
5398
|
-
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.",
|
|
5399
5468
|
inputSchema: {
|
|
5400
5469
|
type: "object",
|
|
5401
5470
|
properties: {
|
|
@@ -5418,6 +5487,15 @@ var TOOLS = {
|
|
|
5418
5487
|
type: "string",
|
|
5419
5488
|
enum: ["active", "deprecated"],
|
|
5420
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."
|
|
5421
5499
|
}
|
|
5422
5500
|
},
|
|
5423
5501
|
required: ["playbookId"]
|
|
@@ -5882,7 +5960,10 @@ async function handleToolCall(name, args, deps) {
|
|
|
5882
5960
|
const where = resolved.project.workspaceName ? `project "${resolved.project.name ?? resolved.project.id}" (workspace "${resolved.project.workspaceName}")` : `project "${resolved.project.name ?? resolved.project.id}"`;
|
|
5883
5961
|
const established = activeProjectId == null;
|
|
5884
5962
|
if (established) {
|
|
5885
|
-
deps.
|
|
5963
|
+
deps.setActiveContext({
|
|
5964
|
+
projectId: resolved.project.id,
|
|
5965
|
+
workspaceId: resolved.project.workspaceId
|
|
5966
|
+
});
|
|
5886
5967
|
}
|
|
5887
5968
|
return {
|
|
5888
5969
|
success: true,
|
|
@@ -6321,15 +6402,57 @@ ${options}
|
|
|
6321
6402
|
}
|
|
6322
6403
|
case "harmony_set_project_context": {
|
|
6323
6404
|
const projectId = z.string().uuid().parse(args.projectId);
|
|
6324
|
-
|
|
6325
|
-
|
|
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
|
+
};
|
|
6326
6443
|
}
|
|
6327
6444
|
case "harmony_get_context": {
|
|
6445
|
+
const report = describeActiveContext({
|
|
6446
|
+
projectId: deps.getActiveProjectId(),
|
|
6447
|
+
workspaceId: deps.getActiveWorkspaceId()
|
|
6448
|
+
});
|
|
6328
6449
|
return {
|
|
6329
6450
|
success: true,
|
|
6330
6451
|
context: {
|
|
6331
|
-
activeWorkspaceId:
|
|
6332
|
-
activeProjectId:
|
|
6452
|
+
activeWorkspaceId: report.workspaceId,
|
|
6453
|
+
activeProjectId: report.projectId,
|
|
6454
|
+
consistent: report.consistent,
|
|
6455
|
+
...report.note ? { note: report.note } : {}
|
|
6333
6456
|
}
|
|
6334
6457
|
};
|
|
6335
6458
|
}
|
|
@@ -7178,7 +7301,10 @@ ${options}
|
|
|
7178
7301
|
workspaceId,
|
|
7179
7302
|
name: name2,
|
|
7180
7303
|
description: args.description,
|
|
7181
|
-
steps: args.steps
|
|
7304
|
+
steps: args.steps,
|
|
7305
|
+
triggerType: args.triggerType,
|
|
7306
|
+
autoBind: args.autoBind,
|
|
7307
|
+
catalogId: args.catalogId
|
|
7182
7308
|
});
|
|
7183
7309
|
return { success: true, playbook: result.playbook };
|
|
7184
7310
|
}
|
|
@@ -7189,7 +7315,9 @@ ${options}
|
|
|
7189
7315
|
description: args.description,
|
|
7190
7316
|
steps: args.steps,
|
|
7191
7317
|
enabled: args.enabled,
|
|
7192
|
-
state: args.state
|
|
7318
|
+
state: args.state,
|
|
7319
|
+
triggerType: args.triggerType,
|
|
7320
|
+
..."autoBind" in args ? { autoBind: args.autoBind } : {}
|
|
7193
7321
|
});
|
|
7194
7322
|
return { success: true, playbook: result.playbook };
|
|
7195
7323
|
}
|
|
@@ -7266,8 +7394,10 @@ ${options}
|
|
|
7266
7394
|
apiUrl: deps.getApiUrl()
|
|
7267
7395
|
});
|
|
7268
7396
|
deps.saveConfig({ apiKey: result.apiKey.rawKey });
|
|
7269
|
-
deps.
|
|
7270
|
-
|
|
7397
|
+
deps.setActiveContext({
|
|
7398
|
+
projectId: result.project.id,
|
|
7399
|
+
workspaceId: result.workspace.id
|
|
7400
|
+
});
|
|
7271
7401
|
deps.resetClient();
|
|
7272
7402
|
return {
|
|
7273
7403
|
success: true,
|
|
@@ -7289,7 +7419,7 @@ function createConfigDeps() {
|
|
|
7289
7419
|
isConfigured,
|
|
7290
7420
|
getActiveProjectId: () => getActiveProjectId(),
|
|
7291
7421
|
getActiveWorkspaceId: () => getActiveWorkspaceId(),
|
|
7292
|
-
|
|
7422
|
+
setActiveContext: (context) => setActiveContext(context),
|
|
7293
7423
|
setActiveWorkspace: (id) => setActiveWorkspace(id),
|
|
7294
7424
|
getApiUrl,
|
|
7295
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();
|