@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/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 = getLocalConfigPath(cwd);
85
- if (!existsSync(localConfigPath)) {
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 existsSync(getLocalConfigPath(cwd));
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 setActiveWorkspace(workspaceId, options) {
137
- if (options?.local) {
138
- saveLocalConfig({ workspaceId }, options.cwd);
139
- } else {
140
- saveConfig({ activeWorkspaceId: workspaceId });
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
- function setActiveProject(projectId, options) {
144
- if (options?.local) {
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({ activeProjectId: projectId });
164
+ saveConfig({
165
+ activeWorkspaceId: context.workspaceId,
166
+ activeProjectId: context.projectId
167
+ });
148
168
  }
149
169
  }
150
- function getActiveWorkspaceId(cwd) {
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?.workspaceId) {
153
- return localConfig.workspaceId;
180
+ if (localConfig) {
181
+ return {
182
+ workspaceId: localConfig.workspaceId ?? null,
183
+ projectId: localConfig.projectId ?? null
184
+ };
154
185
  }
155
- return loadConfig().activeWorkspaceId;
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
- const localConfig = loadLocalConfig(cwd);
159
- if (localConfig?.projectId) {
160
- return localConfig.projectId;
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 loadConfig().activeProjectId;
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((resolve) => setTimeout(resolve, ms));
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((resolve) => setTimeout(resolve, ms));
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((resolve) => this.queue.push(resolve));
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((resolve) => setTimeout(resolve, 2000));
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 = dirname(filePath);
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 = dirname(dirname(samplePath));
3590
+ const parentDir = dirname2(dirname2(samplePath));
3539
3591
  siblingPath = `${parentDir}/${name}/SKILL.md`;
3540
3592
  } else {
3541
- const parentDir = dirname(samplePath);
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, or lifecycle state ('active'|'deprecated').",
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.setActiveProject(resolved.project.id);
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
- deps.setActiveProject(projectId);
6325
- return { success: true, activeProjectId: projectId };
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: deps.getActiveWorkspaceId(),
6332
- activeProjectId: deps.getActiveProjectId()
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.setActiveWorkspace(result.workspace.id);
7269
- deps.setActiveProject(result.project.id);
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
- setActiveProject: (id) => setActiveProject(id),
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 dirname3, join as join8 } from "node:path";
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 = resolve(cwd);
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 = resolve(projectRoot, refPath);
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 = resolve(cwd);
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 = resolve(root, refPath);
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 dirname2 } from "node:path";
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(dirname2(filePath));
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(dirname2(filePath));
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(dirname2(filePath));
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((resolve2) => setTimeout(resolve2, 50));
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 = dirname3(settingsPath);
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 = dirname3(settingsPath);
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
- setActiveWorkspace(selectedWorkspaceIdFromSignup);
8944
- setActiveProject(selectedProjectIdFromSignup);
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 = dirname3(symlink.link);
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
- setActiveWorkspace(selectedWorkspaceId);
9345
- if (selectedProjectId)
9346
- setActiveProject(selectedProjectId);
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 Kanban board").version(version);
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: ${getLocalConfigPath()}`);
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 wsSource = localConfig?.workspaceId ? "local" : globalConfig.activeWorkspaceId ? "global" : "";
9455
- const projSource = localConfig?.projectId ? "local" : globalConfig.activeProjectId ? "global" : "";
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("Smart 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) => {
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,