@gethmy/mcp 3.5.0 → 3.7.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 CHANGED
@@ -434,7 +434,7 @@ curl -X GET "https://gethmy.com/api/v1/workspaces" \
434
434
 
435
435
  ### Global Configuration
436
436
 
437
- Stored in `~/.harmony-mcp/config.json`. Browser sign-in writes OAuth tokens (and nulls `apiKey`); API-key setup writes `apiKey` instead. The server prefers a live OAuth token and refreshes it automatically:
437
+ Stored in `~/.hmy/agent/config.json`. Browser sign-in writes OAuth tokens (and nulls `apiKey`); API-key setup writes `apiKey` instead. The server prefers a live OAuth token and refreshes it automatically:
438
438
 
439
439
  ```json
440
440
  {
@@ -452,7 +452,7 @@ Stored in `~/.harmony-mcp/config.json`. Browser sign-in writes OAuth tokens (and
452
452
 
453
453
  ### Local Project Configuration
454
454
 
455
- Stored in `.harmony-mcp.json` in your project root:
455
+ Stored in `.hmy.json` in your project root:
456
456
 
457
457
  ```json
458
458
  {
@@ -498,10 +498,10 @@ Skills:
498
498
  ~/.agents/skills/hmy/SKILL.md
499
499
 
500
500
  Context:
501
- Local config: .harmony-mcp.json
501
+ Local config: .hmy.json
502
502
  Workspace: my-team-id
503
503
  Project: my-project-id
504
- Global config: ~/.harmony-mcp/config.json
504
+ Global config: ~/.hmy/agent/config.json
505
505
  Workspace: (not set)
506
506
  Project: (not set)
507
507
 
@@ -515,7 +515,7 @@ Context:
515
515
  Add this to prevent accidentally committing local config:
516
516
 
517
517
  ```
518
- .harmony-mcp.json
518
+ .hmy.json
519
519
  ```
520
520
 
521
521
  ## Architecture
package/dist/cli.js CHANGED
@@ -21,12 +21,36 @@ var __require = /* @__PURE__ */ createRequire(import.meta.url);
21
21
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
22
22
  import { homedir } from "node:os";
23
23
  import { dirname, join, parse, resolve } from "node:path";
24
+ function noteLegacyConfigDir(path) {
25
+ if (warnedLegacyConfigDir)
26
+ return;
27
+ warnedLegacyConfigDir = true;
28
+ console.error(`Harmony: reading the pre-#1082 config at ${path}. ` + `The current location is ${getConfigPath()}; ` + `run the agent daemon once to migrate, or move the file yourself.`);
29
+ }
30
+ function noteLegacyLocalPin(path) {
31
+ if (warnedLegacyLocalPin)
32
+ return;
33
+ warnedLegacyLocalPin = true;
34
+ console.error(`Harmony: this repo is pinned by ${path}, the pre-#1082 name. ` + `Rename it to ${LOCAL_CONFIG_FILENAME} — the fallback that finds it is temporary.`);
35
+ }
36
+ function noteLocalPinRename(from, to) {
37
+ console.error(`Harmony: wrote the repo pin to ${to} (the pre-#1082 ${from} is now ignored and can be deleted).`);
38
+ }
39
+ function getHmyRootDir() {
40
+ return join(homedir(), CONFIG_DIR_NAME);
41
+ }
24
42
  function getConfigDir() {
25
- return join(homedir(), ".harmony-mcp");
43
+ return join(getHmyRootDir(), CONFIG_DIR_SUBDIR);
44
+ }
45
+ function getLegacyConfigDir() {
46
+ return join(homedir(), LEGACY_CONFIG_DIR_NAME);
26
47
  }
27
48
  function getConfigPath() {
28
49
  return join(getConfigDir(), "config.json");
29
50
  }
51
+ function getLegacyConfigPath() {
52
+ return join(getLegacyConfigDir(), "config.json");
53
+ }
30
54
  function getLocalConfigPath(cwd) {
31
55
  return join(cwd || process.cwd(), LOCAL_CONFIG_FILENAME);
32
56
  }
@@ -36,9 +60,14 @@ function findLocalConfigPath(cwd) {
36
60
  const { root } = parse(dir);
37
61
  for (;; ) {
38
62
  if (dir !== home && dir !== root) {
39
- const candidate = join(dir, LOCAL_CONFIG_FILENAME);
40
- if (existsSync(candidate))
41
- return candidate;
63
+ const current = join(dir, LOCAL_CONFIG_FILENAME);
64
+ if (existsSync(current))
65
+ return current;
66
+ const legacy = join(dir, LEGACY_LOCAL_CONFIG_FILENAME);
67
+ if (existsSync(legacy)) {
68
+ noteLegacyLocalPin(legacy);
69
+ return legacy;
70
+ }
42
71
  }
43
72
  const parent = dirname(dir);
44
73
  if (parent === dir)
@@ -61,9 +90,12 @@ function emptyConfig() {
61
90
  };
62
91
  }
63
92
  function loadConfig() {
64
- const configPath = getConfigPath();
93
+ let configPath = getConfigPath();
65
94
  if (!existsSync(configPath)) {
66
- return emptyConfig();
95
+ configPath = getLegacyConfigPath();
96
+ if (!existsSync(configPath))
97
+ return emptyConfig();
98
+ noteLegacyConfigDir(configPath);
67
99
  }
68
100
  try {
69
101
  const data = readFileSync(configPath, "utf-8");
@@ -113,7 +145,11 @@ function loadLocalConfig(cwd) {
113
145
  }
114
146
  }
115
147
  function saveLocalConfig(config, cwd) {
116
- const localConfigPath = findLocalConfigPath(cwd) ?? getLocalConfigPath(cwd);
148
+ const foundPath = findLocalConfigPath(cwd);
149
+ const localConfigPath = foundPath ? join(dirname(foundPath), LOCAL_CONFIG_FILENAME) : getLocalConfigPath(cwd);
150
+ if (foundPath !== null && foundPath !== localConfigPath) {
151
+ noteLocalPinRename(foundPath, localConfigPath);
152
+ }
117
153
  const existingConfig = loadLocalConfig(cwd) || {
118
154
  workspaceId: null,
119
155
  projectId: null
@@ -125,6 +161,7 @@ function saveLocalConfig(config, cwd) {
125
161
  if (newConfig.projectId)
126
162
  cleanConfig.projectId = newConfig.projectId;
127
163
  writeFileSync(localConfigPath, JSON.stringify(cleanConfig, null, 2));
164
+ return localConfigPath;
128
165
  }
129
166
  function hasLocalConfig(cwd) {
130
167
  return findLocalConfigPath(cwd) !== null;
@@ -259,7 +296,7 @@ function getMemoryDir() {
259
296
  return config.memoryDir;
260
297
  return join(homedir(), ".harmony", "memory");
261
298
  }
262
- var DEFAULT_API_URL = "https://app.gethmy.com/api", LOCAL_CONFIG_FILENAME = ".harmony-mcp.json";
299
+ var DEFAULT_API_URL = "https://app.gethmy.com/api", LOCAL_CONFIG_FILENAME = ".hmy.json", LEGACY_LOCAL_CONFIG_FILENAME = ".harmony-mcp.json", CONFIG_DIR_NAME = ".hmy", CONFIG_DIR_SUBDIR = "agent", LEGACY_CONFIG_DIR_NAME = ".harmony-mcp", warnedLegacyConfigDir = false, warnedLegacyLocalPin = false;
263
300
  var init_config = () => {};
264
301
 
265
302
  // src/prompt-builder.ts
@@ -2167,6 +2204,213 @@ var REVIEW_DISALLOWED_TOOLS = [
2167
2204
  "mcp__harmony__harmony_delete_subtask",
2168
2205
  "mcp__harmony__harmony_toggle_subtask"
2169
2206
  ];
2207
+ // ../harmony-shared/dist/runRedaction.js
2208
+ var MAX_INPUT_CHARS = 2000;
2209
+ var MAX_OUTPUT_CHARS = 4000;
2210
+ var MAX_INPUT_STRING_CHARS = 600;
2211
+ var REDACTION_MARK = "«redacted»";
2212
+ var SENSITIVE_SEGMENTS = [
2213
+ ".ssh",
2214
+ ".gnupg",
2215
+ ".aws",
2216
+ ".codex",
2217
+ ".gemini",
2218
+ ".docker",
2219
+ ".kube",
2220
+ ".hmy",
2221
+ ".harmony-mcp",
2222
+ ".password-store",
2223
+ ".claude",
2224
+ "gh",
2225
+ "gcloud",
2226
+ "op",
2227
+ "anthropic"
2228
+ ];
2229
+ var CONFIG_SCOPED_SEGMENTS = new Set([
2230
+ "gh",
2231
+ "gcloud",
2232
+ "op",
2233
+ "anthropic"
2234
+ ]);
2235
+ var SENSITIVE_BASENAMES = new Set([
2236
+ ".netrc",
2237
+ "_netrc",
2238
+ ".npmrc",
2239
+ ".pgpass",
2240
+ ".git-credentials",
2241
+ ".htpasswd",
2242
+ ".claude.json",
2243
+ "credentials",
2244
+ ".credentials",
2245
+ "credentials.json",
2246
+ ".credentials.json",
2247
+ "credentials.yml",
2248
+ "credentials.yaml",
2249
+ "auth.json",
2250
+ ".auth.json",
2251
+ "secrets",
2252
+ "secrets.json",
2253
+ "secrets.yaml",
2254
+ "secrets.yml",
2255
+ "id_rsa",
2256
+ "id_dsa",
2257
+ "id_ecdsa",
2258
+ "id_ed25519",
2259
+ "known_hosts"
2260
+ ]);
2261
+ var SENSITIVE_EXTENSIONS = [
2262
+ ".pem",
2263
+ ".key",
2264
+ ".p12",
2265
+ ".pfx",
2266
+ ".keystore",
2267
+ ".jks",
2268
+ ".asc",
2269
+ ".gpg"
2270
+ ];
2271
+ function isSensitivePath(rawPath) {
2272
+ if (typeof rawPath !== "string" || rawPath.length === 0)
2273
+ return false;
2274
+ const path = rawPath.trim().toLowerCase();
2275
+ const segments = path.split(/[\\/]+/).filter((s) => s.length > 0);
2276
+ if (segments.length === 0)
2277
+ return false;
2278
+ for (let i = 0;i < segments.length; i++) {
2279
+ const segment = segments[i];
2280
+ if (!SENSITIVE_SEGMENTS.includes(segment))
2281
+ continue;
2282
+ if (CONFIG_SCOPED_SEGMENTS.has(segment)) {
2283
+ if (i > 0 && segments[i - 1] === ".config")
2284
+ return true;
2285
+ continue;
2286
+ }
2287
+ return true;
2288
+ }
2289
+ const basename = segments[segments.length - 1];
2290
+ if (SENSITIVE_BASENAMES.has(basename))
2291
+ return true;
2292
+ if (basename === ".env" || basename.startsWith(".env."))
2293
+ return true;
2294
+ if (basename.endsWith(".env"))
2295
+ return true;
2296
+ if (SENSITIVE_EXTENSIONS.some((ext) => basename.endsWith(ext)))
2297
+ return true;
2298
+ if (/service[-_]?account.*\.json$/.test(basename))
2299
+ return true;
2300
+ return false;
2301
+ }
2302
+ function sensitivePathsIn(input, depth = 0) {
2303
+ if (depth > 6)
2304
+ return [];
2305
+ if (typeof input === "string") {
2306
+ return isSensitivePath(input) ? [input] : [];
2307
+ }
2308
+ if (Array.isArray(input)) {
2309
+ return input.flatMap((item) => sensitivePathsIn(item, depth + 1));
2310
+ }
2311
+ if (input !== null && typeof input === "object") {
2312
+ return Object.values(input).flatMap((value) => sensitivePathsIn(value, depth + 1));
2313
+ }
2314
+ return [];
2315
+ }
2316
+ var SECRET_PATTERNS = [
2317
+ {
2318
+ pattern: /-----BEGIN[^-]*PRIVATE KEY-----[\s\S]*?-----END[^-]*-----/g,
2319
+ replace: REDACTION_MARK
2320
+ },
2321
+ { pattern: /\bhmy_at_[A-Za-z0-9_-]{8,}/g, replace: REDACTION_MARK },
2322
+ { pattern: /\bhmy_[A-Za-z0-9_-]{8,}/g, replace: REDACTION_MARK },
2323
+ { pattern: /\bsk-(?:ant-)?[A-Za-z0-9_-]{16,}/g, replace: REDACTION_MARK },
2324
+ { pattern: /\bgh[pousr]_[A-Za-z0-9]{16,}/g, replace: REDACTION_MARK },
2325
+ { pattern: /\bgithub_pat_[A-Za-z0-9_]{20,}/g, replace: REDACTION_MARK },
2326
+ { pattern: /\bxox[abprs]-[A-Za-z0-9-]{10,}/g, replace: REDACTION_MARK },
2327
+ { pattern: /\bAKIA[0-9A-Z]{16}\b/g, replace: REDACTION_MARK },
2328
+ { pattern: /\bAIza[0-9A-Za-z_-]{20,}/g, replace: REDACTION_MARK },
2329
+ {
2330
+ pattern: /\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}/g,
2331
+ replace: REDACTION_MARK
2332
+ },
2333
+ {
2334
+ pattern: /\b(Bearer|Basic|Token)\s+[A-Za-z0-9._~+/=-]{12,}/gi,
2335
+ replace: `$1 ${REDACTION_MARK}`
2336
+ },
2337
+ {
2338
+ pattern: /(\w{1,32}:\/\/)[^/\s:@]+:[^/\s@]+@/g,
2339
+ replace: `$1${REDACTION_MARK}@`
2340
+ },
2341
+ {
2342
+ pattern: /\b([A-Za-z0-9_]{0,40}(?:TOKEN|SECRET|PASSWORD|PASSWD|APIKEY|API_KEY|ACCESS_KEY|PRIVATE_KEY|CREDENTIAL|AUTH)[A-Za-z0-9_]{0,40})\s*[=:]\s*(?:"[^"]*"|'[^']*'|`[^`]*`|[^\s,;)}\]]+)/gi,
2343
+ replace: `$1=${REDACTION_MARK}`
2344
+ },
2345
+ {
2346
+ pattern: /(--?(?:password|passwd|token|api-?key|secret|auth)(?:=|\s+))(?:"[^"]*"|'[^']*'|[^\s]+)/gi,
2347
+ replace: `$1${REDACTION_MARK}`
2348
+ }
2349
+ ];
2350
+ function redactSecrets(text) {
2351
+ if (typeof text !== "string" || text.length === 0)
2352
+ return text;
2353
+ let out = text;
2354
+ for (const { pattern, replace } of SECRET_PATTERNS) {
2355
+ pattern.lastIndex = 0;
2356
+ out = out.replace(pattern, replace);
2357
+ }
2358
+ return out;
2359
+ }
2360
+ function truncate(text, max, originalLength) {
2361
+ const total = originalLength ?? text.length;
2362
+ if (total <= max)
2363
+ return text;
2364
+ return `${text.slice(0, max)}… [+${total - max} chars]`;
2365
+ }
2366
+ function redactThenTruncate(text, max) {
2367
+ const preCap = max * 4 + 64;
2368
+ const scanned = text.length > preCap ? text.slice(0, preCap) : text;
2369
+ return truncate(redactSecrets(scanned), max, text.length);
2370
+ }
2371
+ function redactStructure(value, depth = 0) {
2372
+ if (depth > 6)
2373
+ return REDACTION_MARK;
2374
+ if (typeof value === "string") {
2375
+ return redactThenTruncate(value, MAX_INPUT_STRING_CHARS);
2376
+ }
2377
+ if (Array.isArray(value)) {
2378
+ return value.slice(0, 20).map((item) => redactStructure(item, depth + 1));
2379
+ }
2380
+ if (value !== null && typeof value === "object") {
2381
+ const out = {};
2382
+ for (const [key, item] of Object.entries(value)) {
2383
+ out[key] = redactStructure(item, depth + 1);
2384
+ }
2385
+ return out;
2386
+ }
2387
+ return value;
2388
+ }
2389
+ function redactToolCall(args) {
2390
+ const sensitive = sensitivePathsIn(args.input);
2391
+ if (sensitive.length > 0) {
2392
+ return { withheld: "sensitive-path" };
2393
+ }
2394
+ const result = {};
2395
+ if (args.input !== undefined) {
2396
+ let input = redactStructure(args.input);
2397
+ let serialized;
2398
+ try {
2399
+ serialized = JSON.stringify(input) ?? "";
2400
+ } catch {
2401
+ serialized = "";
2402
+ input = REDACTION_MARK;
2403
+ }
2404
+ if (serialized.length > MAX_INPUT_CHARS) {
2405
+ input = truncate(serialized, MAX_INPUT_CHARS);
2406
+ }
2407
+ result.input = input;
2408
+ }
2409
+ if (typeof args.output === "string" && args.output.length > 0) {
2410
+ result.output = redactThenTruncate(args.output, MAX_OUTPUT_CHARS);
2411
+ }
2412
+ return result;
2413
+ }
2170
2414
  // ../harmony-shared/dist/stageHandoff.js
2171
2415
  var HANDOFF_MARKER = "harmony:stage-handoff";
2172
2416
  var HANDOFF_BLOCK_RE = new RegExp("```json\\s*\\n//\\s*" + HANDOFF_MARKER + "\\s*\\n([\\s\\S]*?)\\n```", "m");
@@ -9436,6 +9680,7 @@ function ensureDir(dirPath) {
9436
9680
  mkdirSync5(dirPath, { recursive: true, mode: 493 });
9437
9681
  }
9438
9682
  }
9683
+ var CONFIG_DIR_MARKERS = [".hmy", ".harmony-mcp"];
9439
9684
  function writeFile(filePath, content, options = {}) {
9440
9685
  const exists = existsSync8(filePath);
9441
9686
  if (exists && !options.force) {
@@ -9443,7 +9688,7 @@ function writeFile(filePath, content, options = {}) {
9443
9688
  }
9444
9689
  try {
9445
9690
  ensureDir(dirname3(filePath));
9446
- const defaultMode = filePath.includes(".harmony-mcp") ? 384 : 420;
9691
+ const defaultMode = CONFIG_DIR_MARKERS.some((marker) => filePath.includes(marker)) ? 384 : 420;
9447
9692
  const mode = options.mode ?? defaultMode;
9448
9693
  writeFileSync5(filePath, content, { mode });
9449
9694
  if (options.mode !== undefined) {
@@ -10612,14 +10857,15 @@ Specify the workspace with --workspace <id>, or select one below.`);
10612
10857
  console.log(` ${colors.dim("Skipped tool allowlist — you'll be prompted per tool, or run /permissions in Claude Code later.")}`);
10613
10858
  }
10614
10859
  }
10860
+ let writtenLocalConfigPath = null;
10615
10861
  if (selectedWorkspaceId || selectedProjectId) {
10616
10862
  const localConfig = {};
10617
10863
  if (selectedWorkspaceId)
10618
10864
  localConfig.workspaceId = selectedWorkspaceId;
10619
10865
  if (selectedProjectId)
10620
10866
  localConfig.projectId = selectedProjectId;
10621
- saveLocalConfig(localConfig, cwd);
10622
- console.log(` ${colors.success("✓")} ${colors.dim(formatPath(getLocalConfigPath(cwd), home))} ${colors.dim("(created)")}`);
10867
+ writtenLocalConfigPath = saveLocalConfig(localConfig, cwd);
10868
+ console.log(` ${colors.success("✓")} ${colors.dim(formatPath(writtenLocalConfigPath, home))} ${colors.dim("(created)")}`);
10623
10869
  if (selectedWorkspaceId || selectedProjectId) {
10624
10870
  setActiveContext({
10625
10871
  workspaceId: selectedWorkspaceId ?? null,
@@ -10649,7 +10895,7 @@ Specify the workspace with --workspace <id>, or select one below.`);
10649
10895
  console.log(` Skills: ${installMode === "global" ? "~/.agents/skills/ (global)" : ".claude/skills/ (local)"}`);
10650
10896
  }
10651
10897
  if (selectedWorkspaceId || selectedProjectId) {
10652
- console.log(` Context: ${formatPath(getLocalConfigPath(cwd), home)}`);
10898
+ console.log(` Context: ${formatPath(writtenLocalConfigPath ?? getLocalConfigPath(cwd), home)}`);
10653
10899
  }
10654
10900
  console.log("");
10655
10901
  console.log(` ${colors.bold("Usage:")}`);