@gethmy/mcp 3.8.0 → 3.9.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 +1 -1
- package/dist/cli.js +530 -155
- package/dist/index.js +135 -95
- package/dist/lib/api-client.js +110 -14
- package/dist/lib/config.js +109 -13
- package/dist/lib/oauth-refresh.js +109 -13
- package/package.json +1 -1
- package/src/api-client.ts +9 -0
- package/src/config.ts +243 -12
- package/src/prompt-builder.ts +1 -1
- package/src/server.ts +43 -4
- package/src/skills.ts +6 -80
- package/src/tui/agent-instructions.ts +335 -0
- package/src/tui/setup.ts +144 -63
- package/src/tui/writer.ts +118 -2
package/src/tui/writer.ts
CHANGED
|
@@ -204,6 +204,112 @@ export function appendToToml(
|
|
|
204
204
|
}
|
|
205
205
|
}
|
|
206
206
|
|
|
207
|
+
/**
|
|
208
|
+
* Markers delimiting the Harmony-owned section of a shared markdown file.
|
|
209
|
+
* Everything between them is package-generated and refreshed on reinstall;
|
|
210
|
+
* everything outside them belongs to the project and is never rewritten.
|
|
211
|
+
*/
|
|
212
|
+
export const MARKDOWN_SECTION_START = "<!-- harmony:start -->";
|
|
213
|
+
export const MARKDOWN_SECTION_END = "<!-- harmony:end -->";
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Locate the Harmony-owned section: the first WELL-FORMED marker pair — a
|
|
217
|
+
* START, the first END after it, and no second START in between.
|
|
218
|
+
*
|
|
219
|
+
* **This choice is the whole safety argument of `mergeMarkdownSection`,** and
|
|
220
|
+
* the two obvious rules are each unsafe in one direction. First-START-first-END
|
|
221
|
+
* swallows everything between a stray START above our section and our real END.
|
|
222
|
+
* Last-START-first-END-after does the mirror: a project quoting both markers
|
|
223
|
+
* BELOW our section loses whatever sits between those two mentions. A file that
|
|
224
|
+
* merely shows the markers inside a code fence hits one or the other.
|
|
225
|
+
*
|
|
226
|
+
* Requiring the span to hold no START defeats both, because a stray marker
|
|
227
|
+
* disqualifies the candidate it sits inside and the scan moves past it. What
|
|
228
|
+
* this does NOT do is notice a file holding two complete sections: it updates
|
|
229
|
+
* the first and leaves the second stale. That is a duplicate to spot by eye,
|
|
230
|
+
* not content destroyed, which is the trade this function exists to make.
|
|
231
|
+
*/
|
|
232
|
+
function findSection(text: string): { start: number; end: number } | null {
|
|
233
|
+
let from = 0;
|
|
234
|
+
while (true) {
|
|
235
|
+
const start = text.indexOf(MARKDOWN_SECTION_START, from);
|
|
236
|
+
if (start === -1) return null;
|
|
237
|
+
const bodyFrom = start + MARKDOWN_SECTION_START.length;
|
|
238
|
+
const end = text.indexOf(MARKDOWN_SECTION_END, bodyFrom);
|
|
239
|
+
if (end === -1) return null;
|
|
240
|
+
const nextStart = text.indexOf(MARKDOWN_SECTION_START, bodyFrom);
|
|
241
|
+
if (nextStart === -1 || nextStart > end) return { start, end };
|
|
242
|
+
// `start` is unpaired. `nextStart > from` always, so the scan terminates.
|
|
243
|
+
from = nextStart;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Merge a Harmony-owned section into a shared markdown file (AGENTS.md).
|
|
249
|
+
*
|
|
250
|
+
* `AGENTS.md` is the project's file, not ours. It may already carry the
|
|
251
|
+
* docs-step scaffold written earlier in the same run, or a file the user has
|
|
252
|
+
* maintained by hand for months. A plain `writeFile` destroyed both: setup runs
|
|
253
|
+
* with `force: true` on every fresh install (`needsSkills`), which bypasses the
|
|
254
|
+
* exists-skip, and the agent files are pushed onto `allFiles` AFTER the docs
|
|
255
|
+
* scaffold — so the last write of `AGENTS.md` won and the scaffold vanished in
|
|
256
|
+
* the same breath as the line announcing it. Hence markers and a merge (#1124).
|
|
257
|
+
*
|
|
258
|
+
* - File absent → create it holding just the section.
|
|
259
|
+
* - A section is present (see `findSection`) → replace what lies between its
|
|
260
|
+
* markers, and only under `force` (matching `appendToToml`) — the section is
|
|
261
|
+
* package-generated, so a reinstall is when it should be refreshed.
|
|
262
|
+
* - No section → append. The file's existing content is never rewritten.
|
|
263
|
+
*/
|
|
264
|
+
export function mergeMarkdownSection(
|
|
265
|
+
filePath: string,
|
|
266
|
+
content: string,
|
|
267
|
+
options: WriteOptions = {},
|
|
268
|
+
): FileResult {
|
|
269
|
+
const section = `${MARKDOWN_SECTION_START}\n${content.trim()}\n${MARKDOWN_SECTION_END}\n`;
|
|
270
|
+
|
|
271
|
+
if (!existsSync(filePath)) {
|
|
272
|
+
try {
|
|
273
|
+
ensureDir(dirname(filePath));
|
|
274
|
+
writeFileSync(filePath, section, { mode: 0o644 });
|
|
275
|
+
return { path: filePath, action: "create" };
|
|
276
|
+
} catch (error) {
|
|
277
|
+
return {
|
|
278
|
+
path: filePath,
|
|
279
|
+
action: "skip",
|
|
280
|
+
error: error instanceof Error ? error.message : String(error),
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
try {
|
|
286
|
+
const existing = readFileSync(filePath, "utf-8");
|
|
287
|
+
const found = findSection(existing);
|
|
288
|
+
|
|
289
|
+
if (found) {
|
|
290
|
+
if (!options.force) return { path: filePath, action: "skip" };
|
|
291
|
+
const updated =
|
|
292
|
+
existing.slice(0, found.start) +
|
|
293
|
+
section.trimEnd() +
|
|
294
|
+
existing.slice(found.end + MARKDOWN_SECTION_END.length);
|
|
295
|
+
writeFileSync(filePath, updated, { mode: 0o644 });
|
|
296
|
+
return { path: filePath, action: "update" };
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// No section we can identify — every byte of this file is the project's.
|
|
300
|
+
// Append, never overwrite, however forceful the caller is.
|
|
301
|
+
const separator = existing.endsWith("\n") ? "\n" : "\n\n";
|
|
302
|
+
writeFileSync(filePath, existing + separator + section, { mode: 0o644 });
|
|
303
|
+
return { path: filePath, action: "merge" };
|
|
304
|
+
} catch (error) {
|
|
305
|
+
return {
|
|
306
|
+
path: filePath,
|
|
307
|
+
action: "skip",
|
|
308
|
+
error: error instanceof Error ? error.message : String(error),
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
207
313
|
/**
|
|
208
314
|
* Write multiple files with progress display
|
|
209
315
|
*/
|
|
@@ -211,7 +317,7 @@ export async function writeFilesWithProgress(
|
|
|
211
317
|
files: Array<{
|
|
212
318
|
path: string;
|
|
213
319
|
content: string;
|
|
214
|
-
type: "text" | "json" | "toml";
|
|
320
|
+
type: "text" | "json" | "toml" | "markdown";
|
|
215
321
|
jsonKey?: string;
|
|
216
322
|
tomlSection?: string;
|
|
217
323
|
mode?: number;
|
|
@@ -232,6 +338,8 @@ export async function writeFilesWithProgress(
|
|
|
232
338
|
result = mergeJsonFile(file.path, jsonContent, options);
|
|
233
339
|
} else if (file.type === "toml" && file.tomlSection) {
|
|
234
340
|
result = appendToToml(file.path, file.tomlSection, file.content, options);
|
|
341
|
+
} else if (file.type === "markdown") {
|
|
342
|
+
result = mergeMarkdownSection(file.path, file.content, options);
|
|
235
343
|
} else {
|
|
236
344
|
result = writeFile(file.path, file.content, {
|
|
237
345
|
...options,
|
|
@@ -256,7 +364,15 @@ export async function writeFilesWithProgress(
|
|
|
256
364
|
} else if (result.action === "skip") {
|
|
257
365
|
console.log(messages.fileSkipped(displayPath));
|
|
258
366
|
} else {
|
|
259
|
-
|
|
367
|
+
// `update` used to fall through to "created" — harmless while it was rare,
|
|
368
|
+
// and a lie the moment AGENTS.md started taking the merge path on every
|
|
369
|
+
// reinstall, telling the user a months-old file was just created (#1124).
|
|
370
|
+
const actionLabel =
|
|
371
|
+
result.action === "create"
|
|
372
|
+
? "created"
|
|
373
|
+
: result.action === "merge"
|
|
374
|
+
? "merged"
|
|
375
|
+
: "updated";
|
|
260
376
|
console.log(
|
|
261
377
|
` ${colors.success("\u2713")} ${colors.dim(displayPath)} ${colors.dim(`(${actionLabel})`)}`,
|
|
262
378
|
);
|