threadnote 0.7.7 → 0.7.8
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 +7 -1
- package/dist/mcp_server.cjs +74 -40
- package/dist/threadnote.cjs +336 -97
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -180,10 +180,15 @@ threadnote update
|
|
|
180
180
|
Check without changing anything:
|
|
181
181
|
|
|
182
182
|
```bash
|
|
183
|
+
threadnote version
|
|
183
184
|
threadnote update --check
|
|
184
185
|
threadnote update --dry-run
|
|
185
186
|
```
|
|
186
187
|
|
|
188
|
+
`threadnote version` prints the installed version, latest npm version, and GitHub release notes. When no update is
|
|
189
|
+
available, it shows the current release notes; when an update is available, it shows every newer version. `threadnote
|
|
190
|
+
update` prints the same full "What's new" diff when an update is available.
|
|
191
|
+
|
|
187
192
|
After updating, restart Cursor, Copilot, Codex, Claude, or open a fresh agent session so MCP tools reload.
|
|
188
193
|
|
|
189
194
|
Some releases include post-update memory migrations. `threadnote update` runs the new package's migration prompt after
|
|
@@ -334,8 +339,9 @@ This is it! Start working with your agents as usual. The agent will automaticall
|
|
|
334
339
|
- `install`: installs `openviking[local-embed]==0.3.12` if missing, creates `~/.openviking` config files if absent,
|
|
335
340
|
writes the command shim, upserts user-level agent instructions, and starts/checks OpenViking health by default. Use
|
|
336
341
|
`--no-start` to skip the health check.
|
|
342
|
+
- `version`: prints the installed Threadnote version, latest npm version, and release notes for newer GitHub releases.
|
|
337
343
|
- `update`: updates the published Threadnote package, then runs `repair` so shims and MCP config point at the new
|
|
338
|
-
version.
|
|
344
|
+
version. When an update is available, it prints release notes for the full version diff.
|
|
339
345
|
- `repair`: fixes install/config/shim/manifest/server health issues and rewrites Codex/Claude/Cursor/Copilot MCP configs
|
|
340
346
|
from the current checkout.
|
|
341
347
|
- `start`: starts `openviking-server` on `127.0.0.1:1933`.
|
package/dist/mcp_server.cjs
CHANGED
|
@@ -28477,25 +28477,25 @@ var Protocol = class {
|
|
|
28477
28477
|
});
|
|
28478
28478
|
}
|
|
28479
28479
|
_resetTimeout(messageId) {
|
|
28480
|
-
const
|
|
28481
|
-
if (!
|
|
28480
|
+
const info2 = this._timeoutInfo.get(messageId);
|
|
28481
|
+
if (!info2)
|
|
28482
28482
|
return false;
|
|
28483
|
-
const totalElapsed = Date.now() -
|
|
28484
|
-
if (
|
|
28483
|
+
const totalElapsed = Date.now() - info2.startTime;
|
|
28484
|
+
if (info2.maxTotalTimeout && totalElapsed >= info2.maxTotalTimeout) {
|
|
28485
28485
|
this._timeoutInfo.delete(messageId);
|
|
28486
28486
|
throw McpError.fromError(ErrorCode.RequestTimeout, "Maximum total timeout exceeded", {
|
|
28487
|
-
maxTotalTimeout:
|
|
28487
|
+
maxTotalTimeout: info2.maxTotalTimeout,
|
|
28488
28488
|
totalElapsed
|
|
28489
28489
|
});
|
|
28490
28490
|
}
|
|
28491
|
-
clearTimeout(
|
|
28492
|
-
|
|
28491
|
+
clearTimeout(info2.timeoutId);
|
|
28492
|
+
info2.timeoutId = setTimeout(info2.onTimeout, info2.timeout);
|
|
28493
28493
|
return true;
|
|
28494
28494
|
}
|
|
28495
28495
|
_cleanupTimeout(messageId) {
|
|
28496
|
-
const
|
|
28497
|
-
if (
|
|
28498
|
-
clearTimeout(
|
|
28496
|
+
const info2 = this._timeoutInfo.get(messageId);
|
|
28497
|
+
if (info2) {
|
|
28498
|
+
clearTimeout(info2.timeoutId);
|
|
28499
28499
|
this._timeoutInfo.delete(messageId);
|
|
28500
28500
|
}
|
|
28501
28501
|
}
|
|
@@ -28540,8 +28540,8 @@ var Protocol = class {
|
|
|
28540
28540
|
this._progressHandlers.clear();
|
|
28541
28541
|
this._taskProgressTokens.clear();
|
|
28542
28542
|
this._pendingDebouncedNotifications.clear();
|
|
28543
|
-
for (const
|
|
28544
|
-
clearTimeout(
|
|
28543
|
+
for (const info2 of this._timeoutInfo.values()) {
|
|
28544
|
+
clearTimeout(info2.timeoutId);
|
|
28545
28545
|
}
|
|
28546
28546
|
this._timeoutInfo.clear();
|
|
28547
28547
|
for (const controller of this._requestHandlerAbortControllers.values()) {
|
|
@@ -30036,8 +30036,8 @@ function validateToolName(name) {
|
|
|
30036
30036
|
function issueToolNameWarning(name, warnings) {
|
|
30037
30037
|
if (warnings.length > 0) {
|
|
30038
30038
|
console.warn(`Tool name validation warning for "${name}":`);
|
|
30039
|
-
for (const
|
|
30040
|
-
console.warn(` - ${
|
|
30039
|
+
for (const warning2 of warnings) {
|
|
30040
|
+
console.warn(` - ${warning2}`);
|
|
30041
30041
|
}
|
|
30042
30042
|
console.warn("Tool registration will proceed, but this may cause compatibility issues.");
|
|
30043
30043
|
console.warn("Consider updating the tool name to conform to the MCP tool naming standard.");
|
|
@@ -33591,6 +33591,39 @@ var import_node_crypto = require("node:crypto");
|
|
|
33591
33591
|
var import_promises = require("node:fs/promises");
|
|
33592
33592
|
var import_node_os = require("node:os");
|
|
33593
33593
|
var import_node_path = require("node:path");
|
|
33594
|
+
|
|
33595
|
+
// src/cli_ui.ts
|
|
33596
|
+
var import_node_process2 = require("node:process");
|
|
33597
|
+
var ANSI = {
|
|
33598
|
+
blue: "\x1B[34m",
|
|
33599
|
+
bold: "\x1B[1m",
|
|
33600
|
+
cyan: "\x1B[36m",
|
|
33601
|
+
dim: "\x1B[2m",
|
|
33602
|
+
green: "\x1B[32m",
|
|
33603
|
+
red: "\x1B[31m",
|
|
33604
|
+
reset: "\x1B[0m",
|
|
33605
|
+
yellow: "\x1B[33m"
|
|
33606
|
+
};
|
|
33607
|
+
function color(name, text) {
|
|
33608
|
+
if (!shouldUseColor()) {
|
|
33609
|
+
return text;
|
|
33610
|
+
}
|
|
33611
|
+
return `${ANSI[name]}${text}${ANSI.reset}`;
|
|
33612
|
+
}
|
|
33613
|
+
function command(text) {
|
|
33614
|
+
return color("cyan", text);
|
|
33615
|
+
}
|
|
33616
|
+
function info(text) {
|
|
33617
|
+
return color("cyan", text);
|
|
33618
|
+
}
|
|
33619
|
+
function warning(text) {
|
|
33620
|
+
return color("yellow", text);
|
|
33621
|
+
}
|
|
33622
|
+
function shouldUseColor() {
|
|
33623
|
+
return import_node_process2.stdout.isTTY === true && process.env.NO_COLOR === void 0 && process.env.CI === void 0 && process.env.TERM !== "dumb";
|
|
33624
|
+
}
|
|
33625
|
+
|
|
33626
|
+
// src/utils.ts
|
|
33594
33627
|
function isJsonObject(value) {
|
|
33595
33628
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
33596
33629
|
}
|
|
@@ -33609,11 +33642,11 @@ function redactText(content) {
|
|
|
33609
33642
|
).replace(/Bearer\s+[A-Za-z0-9._~+/=-]+/gi, "Bearer [REDACTED]").replace(/sk-[A-Za-z0-9_-]{16,}/g, "sk-[REDACTED]").replace(/gh[pousr]_[A-Za-z0-9_]{16,}/g, "gh_[REDACTED]");
|
|
33610
33643
|
}
|
|
33611
33644
|
async function requiredOpenVikingCli() {
|
|
33612
|
-
const
|
|
33613
|
-
if (!
|
|
33645
|
+
const command2 = await findExecutable(["ov", "openviking"]);
|
|
33646
|
+
if (!command2) {
|
|
33614
33647
|
throw new Error("Neither ov nor openviking was found in PATH. Run threadnote install first.");
|
|
33615
33648
|
}
|
|
33616
|
-
return
|
|
33649
|
+
return command2;
|
|
33617
33650
|
}
|
|
33618
33651
|
async function openVikingCliForMode(dryRun) {
|
|
33619
33652
|
if (dryRun) {
|
|
@@ -33621,16 +33654,16 @@ async function openVikingCliForMode(dryRun) {
|
|
|
33621
33654
|
}
|
|
33622
33655
|
return requiredOpenVikingCli();
|
|
33623
33656
|
}
|
|
33624
|
-
async function requiredExecutable(
|
|
33625
|
-
const executable = await findExecutable([
|
|
33657
|
+
async function requiredExecutable(command2) {
|
|
33658
|
+
const executable = await findExecutable([command2]);
|
|
33626
33659
|
if (!executable) {
|
|
33627
|
-
throw new Error(`${
|
|
33660
|
+
throw new Error(`${command2} was not found in PATH.`);
|
|
33628
33661
|
}
|
|
33629
33662
|
return executable;
|
|
33630
33663
|
}
|
|
33631
33664
|
async function findExecutable(commands) {
|
|
33632
|
-
for (const
|
|
33633
|
-
const result = await runCommand("which", [
|
|
33665
|
+
for (const command2 of commands) {
|
|
33666
|
+
const result = await runCommand("which", [command2], { allowFailure: true });
|
|
33634
33667
|
if (result.exitCode === 0 && result.stdout.trim()) {
|
|
33635
33668
|
return result.stdout.trim();
|
|
33636
33669
|
}
|
|
@@ -33639,7 +33672,8 @@ async function findExecutable(commands) {
|
|
|
33639
33672
|
}
|
|
33640
33673
|
async function maybeRun(dryRun, executable, args, options = {}) {
|
|
33641
33674
|
const cwdSuffix = options.cwd ? ` (cwd: ${options.cwd})` : "";
|
|
33642
|
-
|
|
33675
|
+
const label = dryRun ? warning("Would run") : info("Running");
|
|
33676
|
+
console.log(`${label}: ${command(formatShellCommand(executable, args))}${cwdSuffix}`);
|
|
33643
33677
|
if (dryRun) {
|
|
33644
33678
|
return void 0;
|
|
33645
33679
|
}
|
|
@@ -34445,9 +34479,9 @@ async function syncSharedReposBeforeAgentRead(config2) {
|
|
|
34445
34479
|
const remainingBehind = new Set(state.behindTeams);
|
|
34446
34480
|
for (const team of syncTeams) {
|
|
34447
34481
|
try {
|
|
34448
|
-
const
|
|
34449
|
-
if (
|
|
34450
|
-
warnings.push(
|
|
34482
|
+
const warning2 = await runShareSyncQuiet(config2, state, { team });
|
|
34483
|
+
if (warning2) {
|
|
34484
|
+
warnings.push(warning2);
|
|
34451
34485
|
} else {
|
|
34452
34486
|
remainingBehind.delete(team);
|
|
34453
34487
|
syncedTeams.push(team);
|
|
@@ -34529,8 +34563,8 @@ async function refreshShareUpdateState(config2, options) {
|
|
|
34529
34563
|
state,
|
|
34530
34564
|
async () => refreshShareUpdateStateLocked(config2, state, options)
|
|
34531
34565
|
);
|
|
34532
|
-
for (const
|
|
34533
|
-
console.error(
|
|
34566
|
+
for (const warning2 of warnings) {
|
|
34567
|
+
console.error(warning2);
|
|
34534
34568
|
}
|
|
34535
34569
|
}
|
|
34536
34570
|
async function refreshShareUpdateStateLocked(config2, state, options) {
|
|
@@ -34917,14 +34951,14 @@ async function waitForOvQueue(ov, config2, options = {}) {
|
|
|
34917
34951
|
console.error(result.stderr.trim());
|
|
34918
34952
|
}
|
|
34919
34953
|
}
|
|
34920
|
-
function isTransientOvFailure(stderr,
|
|
34954
|
+
function isTransientOvFailure(stderr, stdout2) {
|
|
34921
34955
|
const output = `${stderr}
|
|
34922
|
-
${
|
|
34956
|
+
${stdout2}`.toLowerCase();
|
|
34923
34957
|
return output.includes("resource is busy") || output.includes("resource is being processed") || output.includes("network error") || output.includes("error sending request") || output.includes("http request failed") || output.includes("connection refused") || output.includes("connection reset") || output.includes("timed out");
|
|
34924
34958
|
}
|
|
34925
|
-
function isResourceBusyFailure(stderr,
|
|
34959
|
+
function isResourceBusyFailure(stderr, stdout2) {
|
|
34926
34960
|
const output = `${stderr}
|
|
34927
|
-
${
|
|
34961
|
+
${stdout2}`.toLowerCase();
|
|
34928
34962
|
return output.includes("resource is busy") || output.includes("resource is being processed");
|
|
34929
34963
|
}
|
|
34930
34964
|
async function ingestSingleFile(ov, config2, uri, filePath, initialMode, options = {}) {
|
|
@@ -35428,8 +35462,8 @@ ${exactMatches}`);
|
|
|
35428
35462
|
if (syncedTeams.length > 0) {
|
|
35429
35463
|
sections.push(`Auto-synced shared memories: ${syncedTeams.join(", ")}`);
|
|
35430
35464
|
}
|
|
35431
|
-
for (const
|
|
35432
|
-
sections.push(`Auto-sync warning: ${
|
|
35465
|
+
for (const warning2 of syncWarnings) {
|
|
35466
|
+
sections.push(`Auto-sync warning: ${warning2}`);
|
|
35433
35467
|
}
|
|
35434
35468
|
if (sections.length <= 1) {
|
|
35435
35469
|
return semanticResult;
|
|
@@ -35527,7 +35561,7 @@ function registerReadTool(server, config2, name, description) {
|
|
|
35527
35561
|
}
|
|
35528
35562
|
const syncMessages = [
|
|
35529
35563
|
syncedTeams.length > 0 ? `Auto-synced shared memories: ${syncedTeams.join(", ")}` : void 0,
|
|
35530
|
-
...syncWarnings.map((
|
|
35564
|
+
...syncWarnings.map((warning2) => `Auto-sync warning: ${warning2}`)
|
|
35531
35565
|
].filter((part) => part !== void 0);
|
|
35532
35566
|
return {
|
|
35533
35567
|
...result,
|
|
@@ -36011,9 +36045,9 @@ async function removeVikingResourceWithRetry(ov, config2, uri) {
|
|
|
36011
36045
|
}
|
|
36012
36046
|
return false;
|
|
36013
36047
|
}
|
|
36014
|
-
function isResourceBusy(stderr,
|
|
36048
|
+
function isResourceBusy(stderr, stdout2) {
|
|
36015
36049
|
const output = `${stderr}
|
|
36016
|
-
${
|
|
36050
|
+
${stdout2}`.toLowerCase();
|
|
36017
36051
|
return output.includes("resource is busy") || output.includes("resource is being processed");
|
|
36018
36052
|
}
|
|
36019
36053
|
async function ensureMemoryDirectory(ov, config2, directoryUri) {
|
|
@@ -36291,11 +36325,11 @@ function withIdentity2(config2, args) {
|
|
|
36291
36325
|
return [...args, "--account", config2.account, "--user", config2.user, "--agent-id", config2.agentId];
|
|
36292
36326
|
}
|
|
36293
36327
|
async function requiredOpenVikingCli2() {
|
|
36294
|
-
const
|
|
36295
|
-
if (!
|
|
36328
|
+
const command2 = process.env.THREADNOTE_OV ?? await findExecutable(["ov", "openviking"]) ?? await firstExistingPath([(0, import_node_path4.join)((0, import_node_os3.homedir)(), ".local", "bin", "ov"), (0, import_node_path4.join)((0, import_node_os3.homedir)(), ".local", "bin", "openviking")]);
|
|
36329
|
+
if (!command2) {
|
|
36296
36330
|
throw new Error("Neither ov nor openviking was found. Run threadnote install first.");
|
|
36297
36331
|
}
|
|
36298
|
-
return
|
|
36332
|
+
return command2;
|
|
36299
36333
|
}
|
|
36300
36334
|
async function firstExistingPath(paths) {
|
|
36301
36335
|
for (const path of paths) {
|
package/dist/threadnote.cjs
CHANGED
|
@@ -347,11 +347,11 @@ var require_help = __commonJS({
|
|
|
347
347
|
* @returns {number}
|
|
348
348
|
*/
|
|
349
349
|
longestSubcommandTermLength(cmd, helper) {
|
|
350
|
-
return helper.visibleCommands(cmd).reduce((max,
|
|
350
|
+
return helper.visibleCommands(cmd).reduce((max, command2) => {
|
|
351
351
|
return Math.max(
|
|
352
352
|
max,
|
|
353
353
|
this.displayWidth(
|
|
354
|
-
helper.styleSubcommandTerm(helper.subcommandTerm(
|
|
354
|
+
helper.styleSubcommandTerm(helper.subcommandTerm(command2))
|
|
355
355
|
)
|
|
356
356
|
);
|
|
357
357
|
}, 0);
|
|
@@ -512,9 +512,9 @@ var require_help = __commonJS({
|
|
|
512
512
|
* @param {Help} helper
|
|
513
513
|
* @returns string[]
|
|
514
514
|
*/
|
|
515
|
-
formatItemList(
|
|
515
|
+
formatItemList(heading2, items, helper) {
|
|
516
516
|
if (items.length === 0) return [];
|
|
517
|
-
return [helper.styleTitle(
|
|
517
|
+
return [helper.styleTitle(heading2), ...items, ""];
|
|
518
518
|
}
|
|
519
519
|
/**
|
|
520
520
|
* Group items by their help group heading.
|
|
@@ -993,8 +993,8 @@ var require_option = __commonJS({
|
|
|
993
993
|
* @param {string} heading
|
|
994
994
|
* @return {Option}
|
|
995
995
|
*/
|
|
996
|
-
helpGroup(
|
|
997
|
-
this.helpGroupHeading =
|
|
996
|
+
helpGroup(heading2) {
|
|
997
|
+
this.helpGroupHeading = heading2;
|
|
998
998
|
return this;
|
|
999
999
|
}
|
|
1000
1000
|
/**
|
|
@@ -1284,8 +1284,8 @@ var require_command = __commonJS({
|
|
|
1284
1284
|
*/
|
|
1285
1285
|
_getCommandAndAncestors() {
|
|
1286
1286
|
const result = [];
|
|
1287
|
-
for (let
|
|
1288
|
-
result.push(
|
|
1287
|
+
for (let command2 = this; command2; command2 = command2.parent) {
|
|
1288
|
+
result.push(command2);
|
|
1289
1289
|
}
|
|
1290
1290
|
return result;
|
|
1291
1291
|
}
|
|
@@ -1726,22 +1726,22 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
1726
1726
|
* @param {Command} command
|
|
1727
1727
|
* @private
|
|
1728
1728
|
*/
|
|
1729
|
-
_registerCommand(
|
|
1729
|
+
_registerCommand(command2) {
|
|
1730
1730
|
const knownBy = (cmd) => {
|
|
1731
1731
|
return [cmd.name()].concat(cmd.aliases());
|
|
1732
1732
|
};
|
|
1733
|
-
const alreadyUsed = knownBy(
|
|
1733
|
+
const alreadyUsed = knownBy(command2).find(
|
|
1734
1734
|
(name) => this._findCommand(name)
|
|
1735
1735
|
);
|
|
1736
1736
|
if (alreadyUsed) {
|
|
1737
1737
|
const existingCmd = knownBy(this._findCommand(alreadyUsed)).join("|");
|
|
1738
|
-
const newCmd = knownBy(
|
|
1738
|
+
const newCmd = knownBy(command2).join("|");
|
|
1739
1739
|
throw new Error(
|
|
1740
1740
|
`cannot add command '${newCmd}' as already have command '${existingCmd}'`
|
|
1741
1741
|
);
|
|
1742
1742
|
}
|
|
1743
|
-
this._initCommandGroup(
|
|
1744
|
-
this.commands.push(
|
|
1743
|
+
this._initCommandGroup(command2);
|
|
1744
|
+
this.commands.push(command2);
|
|
1745
1745
|
}
|
|
1746
1746
|
/**
|
|
1747
1747
|
* Add an option.
|
|
@@ -2913,12 +2913,12 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
2913
2913
|
let suggestion = "";
|
|
2914
2914
|
if (flag.startsWith("--") && this._showSuggestionAfterError) {
|
|
2915
2915
|
let candidateFlags = [];
|
|
2916
|
-
let
|
|
2916
|
+
let command2 = this;
|
|
2917
2917
|
do {
|
|
2918
|
-
const moreFlags =
|
|
2918
|
+
const moreFlags = command2.createHelp().visibleOptions(command2).filter((option) => option.long).map((option) => option.long);
|
|
2919
2919
|
candidateFlags = candidateFlags.concat(moreFlags);
|
|
2920
|
-
|
|
2921
|
-
} while (
|
|
2920
|
+
command2 = command2.parent;
|
|
2921
|
+
} while (command2 && !command2._enablePositionalOptions);
|
|
2922
2922
|
suggestion = suggestSimilar(flag, candidateFlags);
|
|
2923
2923
|
}
|
|
2924
2924
|
const message = `error: unknown option '${flag}'${suggestion}`;
|
|
@@ -2948,9 +2948,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
2948
2948
|
let suggestion = "";
|
|
2949
2949
|
if (this._showSuggestionAfterError) {
|
|
2950
2950
|
const candidateNames = [];
|
|
2951
|
-
this.createHelp().visibleCommands(this).forEach((
|
|
2952
|
-
candidateNames.push(
|
|
2953
|
-
if (
|
|
2951
|
+
this.createHelp().visibleCommands(this).forEach((command2) => {
|
|
2952
|
+
candidateNames.push(command2.name());
|
|
2953
|
+
if (command2.alias()) candidateNames.push(command2.alias());
|
|
2954
2954
|
});
|
|
2955
2955
|
suggestion = suggestSimilar(unknownName, candidateNames);
|
|
2956
2956
|
}
|
|
@@ -3021,11 +3021,11 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
3021
3021
|
*/
|
|
3022
3022
|
alias(alias) {
|
|
3023
3023
|
if (alias === void 0) return this._aliases[0];
|
|
3024
|
-
let
|
|
3024
|
+
let command2 = this;
|
|
3025
3025
|
if (this.commands.length !== 0 && this.commands[this.commands.length - 1]._executableHandler) {
|
|
3026
|
-
|
|
3026
|
+
command2 = this.commands[this.commands.length - 1];
|
|
3027
3027
|
}
|
|
3028
|
-
if (alias ===
|
|
3028
|
+
if (alias === command2._name)
|
|
3029
3029
|
throw new Error("Command alias can't be the same as its name");
|
|
3030
3030
|
const matchingCommand = this.parent?._findCommand(alias);
|
|
3031
3031
|
if (matchingCommand) {
|
|
@@ -3034,7 +3034,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
3034
3034
|
`cannot add alias '${alias}' to command '${this.name()}' as already have command '${existingCmd}'`
|
|
3035
3035
|
);
|
|
3036
3036
|
}
|
|
3037
|
-
|
|
3037
|
+
command2._aliases.push(alias);
|
|
3038
3038
|
return this;
|
|
3039
3039
|
}
|
|
3040
3040
|
/**
|
|
@@ -3088,9 +3088,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
3088
3088
|
* @param {string} [heading]
|
|
3089
3089
|
* @return {Command | string}
|
|
3090
3090
|
*/
|
|
3091
|
-
helpGroup(
|
|
3092
|
-
if (
|
|
3093
|
-
this._helpGroupHeading =
|
|
3091
|
+
helpGroup(heading2) {
|
|
3092
|
+
if (heading2 === void 0) return this._helpGroupHeading ?? "";
|
|
3093
|
+
this._helpGroupHeading = heading2;
|
|
3094
3094
|
return this;
|
|
3095
3095
|
}
|
|
3096
3096
|
/**
|
|
@@ -3106,9 +3106,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
3106
3106
|
* @param {string} [heading]
|
|
3107
3107
|
* @returns {Command | string}
|
|
3108
3108
|
*/
|
|
3109
|
-
commandsGroup(
|
|
3110
|
-
if (
|
|
3111
|
-
this._defaultCommandGroup =
|
|
3109
|
+
commandsGroup(heading2) {
|
|
3110
|
+
if (heading2 === void 0) return this._defaultCommandGroup ?? "";
|
|
3111
|
+
this._defaultCommandGroup = heading2;
|
|
3112
3112
|
return this;
|
|
3113
3113
|
}
|
|
3114
3114
|
/**
|
|
@@ -3124,9 +3124,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
3124
3124
|
* @param {string} [heading]
|
|
3125
3125
|
* @returns {Command | string}
|
|
3126
3126
|
*/
|
|
3127
|
-
optionsGroup(
|
|
3128
|
-
if (
|
|
3129
|
-
this._defaultOptionGroup =
|
|
3127
|
+
optionsGroup(heading2) {
|
|
3128
|
+
if (heading2 === void 0) return this._defaultOptionGroup ?? "";
|
|
3129
|
+
this._defaultOptionGroup = heading2;
|
|
3130
3130
|
return this;
|
|
3131
3131
|
}
|
|
3132
3132
|
/**
|
|
@@ -3246,7 +3246,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
3246
3246
|
write: outputContext.write,
|
|
3247
3247
|
command: this
|
|
3248
3248
|
};
|
|
3249
|
-
this._getCommandAndAncestors().reverse().forEach((
|
|
3249
|
+
this._getCommandAndAncestors().reverse().forEach((command2) => command2.emit("beforeAllHelp", eventContext));
|
|
3250
3250
|
this.emit("beforeHelp", eventContext);
|
|
3251
3251
|
let helpInformation = this.helpInformation({ error: outputContext.error });
|
|
3252
3252
|
if (deprecatedCallback) {
|
|
@@ -3261,7 +3261,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
3261
3261
|
}
|
|
3262
3262
|
this.emit("afterHelp", eventContext);
|
|
3263
3263
|
this._getCommandAndAncestors().forEach(
|
|
3264
|
-
(
|
|
3264
|
+
(command2) => command2.emit("afterAllHelp", eventContext)
|
|
3265
3265
|
);
|
|
3266
3266
|
}
|
|
3267
3267
|
/**
|
|
@@ -3541,6 +3541,83 @@ var import_node_http = require("node:http");
|
|
|
3541
3541
|
var import_node_net = require("node:net");
|
|
3542
3542
|
var import_node_os = require("node:os");
|
|
3543
3543
|
var import_node_path = require("node:path");
|
|
3544
|
+
|
|
3545
|
+
// src/cli_ui.ts
|
|
3546
|
+
var import_node_readline = require("node:readline");
|
|
3547
|
+
var import_node_process = require("node:process");
|
|
3548
|
+
var ANSI = {
|
|
3549
|
+
blue: "\x1B[34m",
|
|
3550
|
+
bold: "\x1B[1m",
|
|
3551
|
+
cyan: "\x1B[36m",
|
|
3552
|
+
dim: "\x1B[2m",
|
|
3553
|
+
green: "\x1B[32m",
|
|
3554
|
+
red: "\x1B[31m",
|
|
3555
|
+
reset: "\x1B[0m",
|
|
3556
|
+
yellow: "\x1B[33m"
|
|
3557
|
+
};
|
|
3558
|
+
function color(name, text) {
|
|
3559
|
+
if (!shouldUseColor()) {
|
|
3560
|
+
return text;
|
|
3561
|
+
}
|
|
3562
|
+
return `${ANSI[name]}${text}${ANSI.reset}`;
|
|
3563
|
+
}
|
|
3564
|
+
function bold(text) {
|
|
3565
|
+
if (!shouldUseColor()) {
|
|
3566
|
+
return text;
|
|
3567
|
+
}
|
|
3568
|
+
return `${ANSI.bold}${text}${ANSI.reset}`;
|
|
3569
|
+
}
|
|
3570
|
+
function command(text) {
|
|
3571
|
+
return color("cyan", text);
|
|
3572
|
+
}
|
|
3573
|
+
function heading(text) {
|
|
3574
|
+
return bold(text);
|
|
3575
|
+
}
|
|
3576
|
+
function info(text) {
|
|
3577
|
+
return color("cyan", text);
|
|
3578
|
+
}
|
|
3579
|
+
function muted(text) {
|
|
3580
|
+
return color("dim", text);
|
|
3581
|
+
}
|
|
3582
|
+
function success(text) {
|
|
3583
|
+
return color("green", text);
|
|
3584
|
+
}
|
|
3585
|
+
function warning(text) {
|
|
3586
|
+
return color("yellow", text);
|
|
3587
|
+
}
|
|
3588
|
+
function failure(text) {
|
|
3589
|
+
return color("red", text);
|
|
3590
|
+
}
|
|
3591
|
+
function keyValue(label, value) {
|
|
3592
|
+
return `${label}: ${value}`;
|
|
3593
|
+
}
|
|
3594
|
+
function shouldUseColor() {
|
|
3595
|
+
return import_node_process.stdout.isTTY === true && process.env.NO_COLOR === void 0 && process.env.CI === void 0 && process.env.TERM !== "dumb";
|
|
3596
|
+
}
|
|
3597
|
+
async function withSpinner(message, action) {
|
|
3598
|
+
if (import_node_process.stdout.isTTY !== true || process.env.CI !== void 0 || process.env.THREADNOTE_NO_SPINNER !== void 0) {
|
|
3599
|
+
return action();
|
|
3600
|
+
}
|
|
3601
|
+
const frames = ["-", "\\", "|", "/"];
|
|
3602
|
+
let frameIndex = 0;
|
|
3603
|
+
const render = () => {
|
|
3604
|
+
(0, import_node_readline.clearLine)(import_node_process.stdout, 0);
|
|
3605
|
+
(0, import_node_readline.cursorTo)(import_node_process.stdout, 0);
|
|
3606
|
+
import_node_process.stdout.write(`${muted(frames[frameIndex])} ${message}`);
|
|
3607
|
+
frameIndex = (frameIndex + 1) % frames.length;
|
|
3608
|
+
};
|
|
3609
|
+
render();
|
|
3610
|
+
const timer = setInterval(render, 100);
|
|
3611
|
+
try {
|
|
3612
|
+
return await action();
|
|
3613
|
+
} finally {
|
|
3614
|
+
clearInterval(timer);
|
|
3615
|
+
(0, import_node_readline.clearLine)(import_node_process.stdout, 0);
|
|
3616
|
+
(0, import_node_readline.cursorTo)(import_node_process.stdout, 0);
|
|
3617
|
+
}
|
|
3618
|
+
}
|
|
3619
|
+
|
|
3620
|
+
// src/utils.ts
|
|
3544
3621
|
function isJsonObject(value) {
|
|
3545
3622
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3546
3623
|
}
|
|
@@ -3630,11 +3707,11 @@ function escapeRegExp(value) {
|
|
|
3630
3707
|
return value.replace(/[\\^$+?.()|[\]{}]/g, "\\$&");
|
|
3631
3708
|
}
|
|
3632
3709
|
async function requiredOpenVikingCli() {
|
|
3633
|
-
const
|
|
3634
|
-
if (!
|
|
3710
|
+
const command2 = await findExecutable(["ov", "openviking"]);
|
|
3711
|
+
if (!command2) {
|
|
3635
3712
|
throw new Error("Neither ov nor openviking was found in PATH. Run threadnote install first.");
|
|
3636
3713
|
}
|
|
3637
|
-
return
|
|
3714
|
+
return command2;
|
|
3638
3715
|
}
|
|
3639
3716
|
async function openVikingCliForMode(dryRun) {
|
|
3640
3717
|
if (dryRun) {
|
|
@@ -3642,16 +3719,16 @@ async function openVikingCliForMode(dryRun) {
|
|
|
3642
3719
|
}
|
|
3643
3720
|
return requiredOpenVikingCli();
|
|
3644
3721
|
}
|
|
3645
|
-
async function requiredExecutable(
|
|
3646
|
-
const executable = await findExecutable([
|
|
3722
|
+
async function requiredExecutable(command2) {
|
|
3723
|
+
const executable = await findExecutable([command2]);
|
|
3647
3724
|
if (!executable) {
|
|
3648
|
-
throw new Error(`${
|
|
3725
|
+
throw new Error(`${command2} was not found in PATH.`);
|
|
3649
3726
|
}
|
|
3650
3727
|
return executable;
|
|
3651
3728
|
}
|
|
3652
3729
|
async function findExecutable(commands) {
|
|
3653
|
-
for (const
|
|
3654
|
-
const result = await runCommand("which", [
|
|
3730
|
+
for (const command2 of commands) {
|
|
3731
|
+
const result = await runCommand("which", [command2], { allowFailure: true });
|
|
3655
3732
|
if (result.exitCode === 0 && result.stdout.trim()) {
|
|
3656
3733
|
return result.stdout.trim();
|
|
3657
3734
|
}
|
|
@@ -3660,7 +3737,8 @@ async function findExecutable(commands) {
|
|
|
3660
3737
|
}
|
|
3661
3738
|
async function maybeRun(dryRun, executable, args, options = {}) {
|
|
3662
3739
|
const cwdSuffix = options.cwd ? ` (cwd: ${options.cwd})` : "";
|
|
3663
|
-
|
|
3740
|
+
const label = dryRun ? warning("Would run") : info("Running");
|
|
3741
|
+
console.log(`${label}: ${command(formatShellCommand(executable, args))}${cwdSuffix}`);
|
|
3664
3742
|
if (dryRun) {
|
|
3665
3743
|
return void 0;
|
|
3666
3744
|
}
|
|
@@ -4090,12 +4168,12 @@ function firstLine(value) {
|
|
|
4090
4168
|
}
|
|
4091
4169
|
function formatStatus(status) {
|
|
4092
4170
|
if (status === "ok") {
|
|
4093
|
-
return "OK ";
|
|
4171
|
+
return success("OK ");
|
|
4094
4172
|
}
|
|
4095
4173
|
if (status === "warn") {
|
|
4096
|
-
return "WARN";
|
|
4174
|
+
return warning("WARN");
|
|
4097
4175
|
}
|
|
4098
|
-
return "FAIL";
|
|
4176
|
+
return failure("FAIL");
|
|
4099
4177
|
}
|
|
4100
4178
|
function formatShellCommand(executable, args) {
|
|
4101
4179
|
return redactText([executable, ...args].map(shellQuote).join(" "));
|
|
@@ -4152,7 +4230,7 @@ async function runMcpInstall(config, agent, options) {
|
|
|
4152
4230
|
});
|
|
4153
4231
|
return;
|
|
4154
4232
|
}
|
|
4155
|
-
const
|
|
4233
|
+
const command2 = buildMcpInstallCommand(config, agent, name, {
|
|
4156
4234
|
bearerTokenEnvVar: options.bearerTokenEnvVar,
|
|
4157
4235
|
nativeHttp,
|
|
4158
4236
|
scope: options.scope,
|
|
@@ -4161,11 +4239,11 @@ async function runMcpInstall(config, agent, options) {
|
|
|
4161
4239
|
const removeCommand = buildMcpRemoveCommand(agent, name);
|
|
4162
4240
|
if (!apply) {
|
|
4163
4241
|
console.log("Dry run. Re-run with --apply to modify the selected agent config.");
|
|
4164
|
-
if (removeCommand.cwd ||
|
|
4165
|
-
console.log(`Command working directory: ${removeCommand.cwd ??
|
|
4242
|
+
if (removeCommand.cwd || command2.cwd) {
|
|
4243
|
+
console.log(`Command working directory: ${removeCommand.cwd ?? command2.cwd}`);
|
|
4166
4244
|
}
|
|
4167
4245
|
console.log(formatShellCommand(removeCommand.executable, removeCommand.args));
|
|
4168
|
-
console.log(formatShellCommand(
|
|
4246
|
+
console.log(formatShellCommand(command2.executable, command2.args));
|
|
4169
4247
|
printMcpSnippet(config, agent, name, { nativeHttp, scope: options.scope, url });
|
|
4170
4248
|
return;
|
|
4171
4249
|
}
|
|
@@ -4173,7 +4251,7 @@ async function runMcpInstall(config, agent, options) {
|
|
|
4173
4251
|
allowFailure: true,
|
|
4174
4252
|
cwd: removeCommand.cwd
|
|
4175
4253
|
});
|
|
4176
|
-
await maybeRun(false,
|
|
4254
|
+
await maybeRun(false, command2.executable, command2.args, { cwd: command2.cwd });
|
|
4177
4255
|
}
|
|
4178
4256
|
async function runCursorMcpInstall(config, name, options) {
|
|
4179
4257
|
const path = cursorMcpConfigPath();
|
|
@@ -4244,8 +4322,8 @@ async function removeMcpConfigs(value, dryRun) {
|
|
|
4244
4322
|
await removeCopilotMcpConfig(OPENVIKING_MCP_NAME, dryRun);
|
|
4245
4323
|
continue;
|
|
4246
4324
|
}
|
|
4247
|
-
const
|
|
4248
|
-
await maybeRun(dryRun,
|
|
4325
|
+
const command2 = buildMcpRemoveCommand(client, OPENVIKING_MCP_NAME);
|
|
4326
|
+
await maybeRun(dryRun, command2.executable, command2.args, { allowFailure: true, cwd: command2.cwd });
|
|
4249
4327
|
}
|
|
4250
4328
|
}
|
|
4251
4329
|
async function removeMcpSnippets(config, dryRun) {
|
|
@@ -4280,17 +4358,17 @@ function buildMcpInstallCommand(config, agent, name, options) {
|
|
|
4280
4358
|
const claudeCwd = getInvocationCwd();
|
|
4281
4359
|
const claudeScope = options.scope ?? "user";
|
|
4282
4360
|
if (!options.nativeHttp) {
|
|
4283
|
-
const
|
|
4361
|
+
const command2 = mcpAdapterCommand();
|
|
4284
4362
|
const env = mcpEnvironment(config);
|
|
4285
4363
|
if (agent === "codex") {
|
|
4286
4364
|
return {
|
|
4287
4365
|
executable: "codex",
|
|
4288
|
-
args: ["mcp", "add", ...env.flatMap((value) => ["--env", value]), name, "--", "/usr/bin/env", ...
|
|
4366
|
+
args: ["mcp", "add", ...env.flatMap((value) => ["--env", value]), name, "--", "/usr/bin/env", ...command2]
|
|
4289
4367
|
};
|
|
4290
4368
|
}
|
|
4291
4369
|
return {
|
|
4292
4370
|
executable: "claude",
|
|
4293
|
-
args: ["mcp", "add", "--scope", claudeScope, name, "--", "/usr/bin/env", ...env, ...
|
|
4371
|
+
args: ["mcp", "add", "--scope", claudeScope, name, "--", "/usr/bin/env", ...env, ...command2],
|
|
4294
4372
|
cwd: claudeCwd
|
|
4295
4373
|
};
|
|
4296
4374
|
}
|
|
@@ -4472,12 +4550,12 @@ function printMcpSnippet(config, agent, name, options) {
|
|
|
4472
4550
|
return;
|
|
4473
4551
|
}
|
|
4474
4552
|
const snippetPath = (0, import_node_path2.join)(config.agentContextHome, "mcp", `${name}.${agent}.${agent === "codex" ? "toml" : "txt"}`);
|
|
4475
|
-
const
|
|
4553
|
+
const command2 = buildMcpInstallCommand(config, agent, name, {
|
|
4476
4554
|
nativeHttp: options.nativeHttp,
|
|
4477
4555
|
scope: options.scope,
|
|
4478
4556
|
url: options.url
|
|
4479
4557
|
});
|
|
4480
|
-
const snippet2 = `${formatShellCommand(
|
|
4558
|
+
const snippet2 = `${formatShellCommand(command2.executable, command2.args)}
|
|
4481
4559
|
`;
|
|
4482
4560
|
console.log(`
|
|
4483
4561
|
Snippet (${snippetPath}):
|
|
@@ -7960,9 +8038,9 @@ async function syncSharedReposBeforeAgentRead(config) {
|
|
|
7960
8038
|
const remainingBehind = new Set(state.behindTeams);
|
|
7961
8039
|
for (const team of syncTeams) {
|
|
7962
8040
|
try {
|
|
7963
|
-
const
|
|
7964
|
-
if (
|
|
7965
|
-
warnings.push(
|
|
8041
|
+
const warning2 = await runShareSyncQuiet(config, state, { team });
|
|
8042
|
+
if (warning2) {
|
|
8043
|
+
warnings.push(warning2);
|
|
7966
8044
|
} else {
|
|
7967
8045
|
remainingBehind.delete(team);
|
|
7968
8046
|
syncedTeams.push(team);
|
|
@@ -8724,14 +8802,14 @@ async function waitForOvQueue(ov, config, options = {}) {
|
|
|
8724
8802
|
console.error(result.stderr.trim());
|
|
8725
8803
|
}
|
|
8726
8804
|
}
|
|
8727
|
-
function isTransientOvFailure(stderr,
|
|
8805
|
+
function isTransientOvFailure(stderr, stdout2) {
|
|
8728
8806
|
const output2 = `${stderr}
|
|
8729
|
-
${
|
|
8807
|
+
${stdout2}`.toLowerCase();
|
|
8730
8808
|
return output2.includes("resource is busy") || output2.includes("resource is being processed") || output2.includes("network error") || output2.includes("error sending request") || output2.includes("http request failed") || output2.includes("connection refused") || output2.includes("connection reset") || output2.includes("timed out");
|
|
8731
8809
|
}
|
|
8732
|
-
function isResourceBusyFailure(stderr,
|
|
8810
|
+
function isResourceBusyFailure(stderr, stdout2) {
|
|
8733
8811
|
const output2 = `${stderr}
|
|
8734
|
-
${
|
|
8812
|
+
${stdout2}`.toLowerCase();
|
|
8735
8813
|
return output2.includes("resource is busy") || output2.includes("resource is being processed");
|
|
8736
8814
|
}
|
|
8737
8815
|
async function ingestSingleFile(ov, config, uri, filePath, initialMode, options = {}) {
|
|
@@ -9166,8 +9244,8 @@ async function syncSharedReposAndLog(config) {
|
|
|
9166
9244
|
if (syncResult.syncedTeams.length > 0) {
|
|
9167
9245
|
console.error(`Auto-synced shared memories: ${syncResult.syncedTeams.join(", ")}`);
|
|
9168
9246
|
}
|
|
9169
|
-
for (const
|
|
9170
|
-
console.error(`Auto-sync warning: ${
|
|
9247
|
+
for (const warning2 of syncResult.warnings) {
|
|
9248
|
+
console.error(`Auto-sync warning: ${warning2}`);
|
|
9171
9249
|
}
|
|
9172
9250
|
} catch (err) {
|
|
9173
9251
|
console.error(`Auto-sync warning: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -9995,9 +10073,9 @@ function localUserMemoriesRoot(config) {
|
|
|
9995
10073
|
function uniqueStrings(values) {
|
|
9996
10074
|
return [...new Set(values)].sort();
|
|
9997
10075
|
}
|
|
9998
|
-
function isResourceBusy(stderr,
|
|
10076
|
+
function isResourceBusy(stderr, stdout2) {
|
|
9999
10077
|
const output2 = `${stderr}
|
|
10000
|
-
${
|
|
10078
|
+
${stdout2}`.toLowerCase();
|
|
10001
10079
|
return output2.includes("resource is busy") || output2.includes("resource is being processed");
|
|
10002
10080
|
}
|
|
10003
10081
|
async function buildHandoff(options) {
|
|
@@ -10787,7 +10865,7 @@ var import_node_fs6 = require("node:fs");
|
|
|
10787
10865
|
var import_promises11 = require("node:fs/promises");
|
|
10788
10866
|
var import_node_os6 = require("node:os");
|
|
10789
10867
|
var import_node_path12 = require("node:path");
|
|
10790
|
-
var
|
|
10868
|
+
var import_node_process3 = require("node:process");
|
|
10791
10869
|
var import_promises12 = require("node:readline/promises");
|
|
10792
10870
|
|
|
10793
10871
|
// src/update.ts
|
|
@@ -10796,7 +10874,106 @@ var import_promises9 = require("node:fs/promises");
|
|
|
10796
10874
|
var import_node_os5 = require("node:os");
|
|
10797
10875
|
var import_node_path11 = require("node:path");
|
|
10798
10876
|
var import_promises10 = require("node:readline/promises");
|
|
10799
|
-
var
|
|
10877
|
+
var import_node_process2 = require("node:process");
|
|
10878
|
+
|
|
10879
|
+
// src/release_notes.ts
|
|
10880
|
+
var GITHUB_RELEASES_URL = "https://api.github.com/repos/Kashkovsky/threadnote/releases?per_page=100";
|
|
10881
|
+
var RELEASE_FETCH_TIMEOUT_MS = 3500;
|
|
10882
|
+
async function fetchThreadnoteReleaseNotes() {
|
|
10883
|
+
const response = await fetch(GITHUB_RELEASES_URL, {
|
|
10884
|
+
headers: {
|
|
10885
|
+
accept: "application/vnd.github+json",
|
|
10886
|
+
"user-agent": "threadnote-cli"
|
|
10887
|
+
},
|
|
10888
|
+
signal: AbortSignal.timeout(RELEASE_FETCH_TIMEOUT_MS)
|
|
10889
|
+
});
|
|
10890
|
+
if (!response.ok) {
|
|
10891
|
+
throw new Error(`GitHub releases returned HTTP ${response.status}`);
|
|
10892
|
+
}
|
|
10893
|
+
const parsed = await response.json();
|
|
10894
|
+
if (!Array.isArray(parsed)) {
|
|
10895
|
+
throw new Error("GitHub releases response was not an array.");
|
|
10896
|
+
}
|
|
10897
|
+
return parsed.flatMap(parseGithubRelease);
|
|
10898
|
+
}
|
|
10899
|
+
function releasesBetween(releases, currentVersion, latestVersion) {
|
|
10900
|
+
return releases.filter(
|
|
10901
|
+
(release) => compareVersions(release.version, currentVersion) > 0 && compareVersions(release.version, latestVersion) <= 0
|
|
10902
|
+
).sort((left, right) => compareVersions(left.version, right.version));
|
|
10903
|
+
}
|
|
10904
|
+
function releaseForVersion(releases, version) {
|
|
10905
|
+
return releases.filter((release) => compareVersions(release.version, version) === 0).slice(0, 1);
|
|
10906
|
+
}
|
|
10907
|
+
function formatWhatsNew(releases) {
|
|
10908
|
+
if (releases.length === 0) {
|
|
10909
|
+
return [];
|
|
10910
|
+
}
|
|
10911
|
+
const lines = ["What's new:"];
|
|
10912
|
+
for (const release of releases) {
|
|
10913
|
+
lines.push(`${release.version}: ${release.title || "Release notes"}`);
|
|
10914
|
+
for (const line of bodyLines(release.body)) {
|
|
10915
|
+
lines.push(` ${line}`);
|
|
10916
|
+
}
|
|
10917
|
+
}
|
|
10918
|
+
return lines;
|
|
10919
|
+
}
|
|
10920
|
+
async function whatsNewLinesForVersion(version) {
|
|
10921
|
+
try {
|
|
10922
|
+
const releases = await fetchThreadnoteReleaseNotes();
|
|
10923
|
+
const lines = formatWhatsNew(releaseForVersion(releases, version));
|
|
10924
|
+
return lines.length > 0 ? lines : ["What's new:", `No GitHub release notes found for ${version}.`];
|
|
10925
|
+
} catch (err) {
|
|
10926
|
+
return [`What's new: unavailable (${errorMessage(err)})`];
|
|
10927
|
+
}
|
|
10928
|
+
}
|
|
10929
|
+
async function whatsNewLinesForVersionRange(currentVersion, latestVersion) {
|
|
10930
|
+
try {
|
|
10931
|
+
const releases = await fetchThreadnoteReleaseNotes();
|
|
10932
|
+
const lines = formatWhatsNew(releasesBetween(releases, currentVersion, latestVersion));
|
|
10933
|
+
return lines.length > 0 ? lines : ["What's new:", "No GitHub release notes found for this version range."];
|
|
10934
|
+
} catch (err) {
|
|
10935
|
+
return [`What's new: unavailable (${errorMessage(err)})`];
|
|
10936
|
+
}
|
|
10937
|
+
}
|
|
10938
|
+
function parseGithubRelease(value) {
|
|
10939
|
+
if (!isJsonObject(value) || value.draft === true || value.prerelease === true) {
|
|
10940
|
+
return [];
|
|
10941
|
+
}
|
|
10942
|
+
const tagName = typeof value.tag_name === "string" ? value.tag_name : "";
|
|
10943
|
+
const version = tagName.trim().replace(/^v/, "");
|
|
10944
|
+
if (!/^\d+\.\d+\.\d+(?:[-.A-Za-z0-9]+)?$/.test(version)) {
|
|
10945
|
+
return [];
|
|
10946
|
+
}
|
|
10947
|
+
const name = typeof value.name === "string" ? value.name : tagName;
|
|
10948
|
+
const body = typeof value.body === "string" ? value.body : "";
|
|
10949
|
+
const url = typeof value.html_url === "string" ? value.html_url : void 0;
|
|
10950
|
+
return [{ body, title: titleFromReleaseName(name, version), url, version }];
|
|
10951
|
+
}
|
|
10952
|
+
function titleFromReleaseName(name, version) {
|
|
10953
|
+
const trimmed = name.trim();
|
|
10954
|
+
const withoutPrefix = trimmed.replace(new RegExp(`^v?${escapeRegExp2(version)}:\\s*`, "i"), "");
|
|
10955
|
+
return withoutPrefix || trimmed || "Release notes";
|
|
10956
|
+
}
|
|
10957
|
+
function bodyLines(body) {
|
|
10958
|
+
const out = [];
|
|
10959
|
+
for (const rawLine of body.split("\n")) {
|
|
10960
|
+
const line = rawLine.trim();
|
|
10961
|
+
if (!line || /^#{1,6}\s+/.test(line)) {
|
|
10962
|
+
continue;
|
|
10963
|
+
}
|
|
10964
|
+
if (/^[-*]\s+/.test(line)) {
|
|
10965
|
+
out.push(`- ${line.replace(/^[-*]\s+/, "")}`);
|
|
10966
|
+
} else {
|
|
10967
|
+
out.push(line);
|
|
10968
|
+
}
|
|
10969
|
+
}
|
|
10970
|
+
return out;
|
|
10971
|
+
}
|
|
10972
|
+
function escapeRegExp2(value) {
|
|
10973
|
+
return value.replace(/[\\^$+?.()|[\]{}]/g, "\\$&");
|
|
10974
|
+
}
|
|
10975
|
+
|
|
10976
|
+
// src/update.ts
|
|
10800
10977
|
var NPM_PACKAGE_NAME = "threadnote";
|
|
10801
10978
|
var DEFAULT_NPM_REGISTRY = "https://registry.npmjs.org/";
|
|
10802
10979
|
var UPDATE_CHECK_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
@@ -10813,43 +10990,47 @@ async function maybeNotifyUpdate(config, options = {}) {
|
|
|
10813
10990
|
return;
|
|
10814
10991
|
}
|
|
10815
10992
|
try {
|
|
10816
|
-
const
|
|
10993
|
+
const info2 = await getUpdateInfo(config, {
|
|
10817
10994
|
allowCacheWrite: options.dryRun !== true,
|
|
10818
10995
|
preferFresh: false,
|
|
10819
10996
|
registry: updateRegistry()
|
|
10820
10997
|
});
|
|
10821
|
-
if (!
|
|
10998
|
+
if (!info2.isUpdateAvailable) {
|
|
10822
10999
|
return;
|
|
10823
11000
|
}
|
|
10824
11001
|
console.log("");
|
|
10825
|
-
console.log(`Update available: threadnote ${
|
|
10826
|
-
console.log(
|
|
11002
|
+
console.log(warning(`Update available: threadnote ${info2.currentVersion} -> ${info2.latestVersion}`));
|
|
11003
|
+
console.log(`Run: ${info("threadnote update")}`);
|
|
10827
11004
|
} catch (_err) {
|
|
10828
11005
|
return;
|
|
10829
11006
|
}
|
|
10830
11007
|
}
|
|
10831
11008
|
async function runUpdate(config, options) {
|
|
10832
11009
|
const registry = normalizeRegistry(options.registry ?? updateRegistry());
|
|
10833
|
-
const
|
|
10834
|
-
|
|
10835
|
-
|
|
10836
|
-
|
|
10837
|
-
|
|
10838
|
-
|
|
10839
|
-
|
|
10840
|
-
|
|
11010
|
+
const info2 = await withSpinner(
|
|
11011
|
+
"Checking npm for latest threadnote version",
|
|
11012
|
+
() => getUpdateInfo(config, {
|
|
11013
|
+
allowCacheWrite: options.dryRun !== true,
|
|
11014
|
+
preferFresh: true,
|
|
11015
|
+
registry
|
|
11016
|
+
})
|
|
11017
|
+
);
|
|
11018
|
+
console.log(keyValue("Current version", info(info2.currentVersion)));
|
|
11019
|
+
console.log(keyValue("Latest version", info(info2.latestVersion)));
|
|
11020
|
+
console.log(keyValue("Registry", info2.registry));
|
|
10841
11021
|
if (options.check === true) {
|
|
10842
|
-
if (
|
|
10843
|
-
console.log(
|
|
11022
|
+
if (info2.isUpdateAvailable) {
|
|
11023
|
+
console.log(warning("Update available. Run: threadnote update"));
|
|
11024
|
+
await printWhatsNewIfAvailable(info2);
|
|
10844
11025
|
} else {
|
|
10845
11026
|
console.log(
|
|
10846
|
-
compareVersions(
|
|
11027
|
+
compareVersions(info2.currentVersion, info2.latestVersion) > 0 ? warning("Current version is newer than npm latest.") : success("Threadnote is up to date.")
|
|
10847
11028
|
);
|
|
10848
11029
|
}
|
|
10849
11030
|
return;
|
|
10850
11031
|
}
|
|
10851
|
-
if (!
|
|
10852
|
-
console.log("Threadnote is up to date.");
|
|
11032
|
+
if (!info2.isUpdateAvailable && options.force !== true) {
|
|
11033
|
+
console.log(success("Threadnote is up to date."));
|
|
10853
11034
|
return;
|
|
10854
11035
|
}
|
|
10855
11036
|
const runtime = await resolveUpdateRuntime(options.runtime ?? "auto");
|
|
@@ -10857,6 +11038,7 @@ async function runUpdate(config, options) {
|
|
|
10857
11038
|
await maybeRun(options.dryRun === true, updateCommand.executable, updateCommand.args);
|
|
10858
11039
|
if (options.repair === false) {
|
|
10859
11040
|
console.log("Skipping repair because --no-repair was provided.");
|
|
11041
|
+
await printWhatsNewIfAvailable(info2);
|
|
10860
11042
|
return;
|
|
10861
11043
|
}
|
|
10862
11044
|
const threadnoteCommand = await installedThreadnoteCommand(runtime);
|
|
@@ -10865,9 +11047,9 @@ async function runUpdate(config, options) {
|
|
|
10865
11047
|
const postUpdateArgs = [
|
|
10866
11048
|
"post-update",
|
|
10867
11049
|
"--from-version",
|
|
10868
|
-
|
|
11050
|
+
info2.currentVersion,
|
|
10869
11051
|
"--to-version",
|
|
10870
|
-
|
|
11052
|
+
info2.latestVersion,
|
|
10871
11053
|
...options.yes === true ? ["--yes"] : []
|
|
10872
11054
|
];
|
|
10873
11055
|
if (options.dryRun === true) {
|
|
@@ -10885,6 +11067,20 @@ async function runUpdate(config, options) {
|
|
|
10885
11067
|
console.log(
|
|
10886
11068
|
"Update complete. Restart Cursor, Copilot, Codex, Claude, or open a fresh agent session so MCP tools reload."
|
|
10887
11069
|
);
|
|
11070
|
+
await printWhatsNewIfAvailable(info2);
|
|
11071
|
+
}
|
|
11072
|
+
async function printWhatsNewIfAvailable(info2) {
|
|
11073
|
+
if (!info2.isUpdateAvailable) {
|
|
11074
|
+
return;
|
|
11075
|
+
}
|
|
11076
|
+
console.log("");
|
|
11077
|
+
const whatsNew = await withSpinner(
|
|
11078
|
+
"Fetching GitHub release notes",
|
|
11079
|
+
() => whatsNewLinesForVersionRange(info2.currentVersion, info2.latestVersion)
|
|
11080
|
+
);
|
|
11081
|
+
for (const line of whatsNew) {
|
|
11082
|
+
console.log(line === "What's new:" ? heading(line) : line);
|
|
11083
|
+
}
|
|
10888
11084
|
}
|
|
10889
11085
|
async function runPostUpdate(config, options) {
|
|
10890
11086
|
if (!options.fromVersion || !options.toVersion) {
|
|
@@ -11202,7 +11398,7 @@ function printPostUpdateMigration(migration) {
|
|
|
11202
11398
|
}
|
|
11203
11399
|
}
|
|
11204
11400
|
async function confirmPostUpdateMigration(prompt, defaultYes = false) {
|
|
11205
|
-
const readline = (0, import_promises10.createInterface)({ input:
|
|
11401
|
+
const readline = (0, import_promises10.createInterface)({ input: import_node_process2.stdin, output: import_node_process2.stdout });
|
|
11206
11402
|
try {
|
|
11207
11403
|
const answer = (await readline.question(prompt)).trim().toLowerCase();
|
|
11208
11404
|
if (answer === "") {
|
|
@@ -11724,8 +11920,8 @@ async function commandPresenceCheck(name, args) {
|
|
|
11724
11920
|
};
|
|
11725
11921
|
}
|
|
11726
11922
|
async function firstCommandCheck(name, commands, args) {
|
|
11727
|
-
for (const
|
|
11728
|
-
const executable = await findExecutable([
|
|
11923
|
+
for (const command2 of commands) {
|
|
11924
|
+
const executable = await findExecutable([command2]);
|
|
11729
11925
|
if (!executable) {
|
|
11730
11926
|
continue;
|
|
11731
11927
|
}
|
|
@@ -11733,7 +11929,7 @@ async function firstCommandCheck(name, commands, args) {
|
|
|
11733
11929
|
return {
|
|
11734
11930
|
name,
|
|
11735
11931
|
status: result.exitCode === 0 ? "ok" : "warn",
|
|
11736
|
-
detail: `${
|
|
11932
|
+
detail: `${command2}: ${firstLine(result.stdout || result.stderr) || executable}`
|
|
11737
11933
|
};
|
|
11738
11934
|
}
|
|
11739
11935
|
return { name, status: "fail", detail: `none found: ${commands.join(", ")}` };
|
|
@@ -11908,13 +12104,13 @@ async function runInstallCommands(config, preferred, force, dryRun) {
|
|
|
11908
12104
|
}
|
|
11909
12105
|
}
|
|
11910
12106
|
async function offerToInstallUv() {
|
|
11911
|
-
if (
|
|
12107
|
+
if (import_node_process3.stdin.isTTY !== true || import_node_process3.stdout.isTTY !== true) {
|
|
11912
12108
|
console.warn(
|
|
11913
12109
|
"Neither uv nor pipx was found on PATH. Falling back to `python3 -m pip install --user`, which fails on PEP 668 (Homebrew/system) Python.\nRe-run with --package-manager uv after installing uv (brew install uv), or pass --package-manager pipx."
|
|
11914
12110
|
);
|
|
11915
12111
|
return false;
|
|
11916
12112
|
}
|
|
11917
|
-
const readline = (0, import_promises12.createInterface)({ input:
|
|
12113
|
+
const readline = (0, import_promises12.createInterface)({ input: import_node_process3.stdin, output: import_node_process3.stdout });
|
|
11918
12114
|
let answer;
|
|
11919
12115
|
try {
|
|
11920
12116
|
answer = (await readline.question(
|
|
@@ -12365,6 +12561,46 @@ function parsePackageManager(value) {
|
|
|
12365
12561
|
throw new Error(`Invalid package manager: ${value}`);
|
|
12366
12562
|
}
|
|
12367
12563
|
|
|
12564
|
+
// src/version_command.ts
|
|
12565
|
+
async function runVersion(config, options) {
|
|
12566
|
+
const currentVersion = await currentPackageVersion();
|
|
12567
|
+
const registry = normalizeRegistry(options.registry ?? updateRegistry());
|
|
12568
|
+
let latestVersion;
|
|
12569
|
+
let latestWarning;
|
|
12570
|
+
try {
|
|
12571
|
+
latestVersion = await withSpinner("Checking npm for latest threadnote version", () => fetchLatestVersion2(registry));
|
|
12572
|
+
} catch (err) {
|
|
12573
|
+
latestWarning = errorMessage(err);
|
|
12574
|
+
}
|
|
12575
|
+
console.log(keyValue("Current version", info(currentVersion)));
|
|
12576
|
+
console.log(keyValue("Latest version", latestVersion ? info(latestVersion) : warning("unavailable")));
|
|
12577
|
+
if (latestWarning) {
|
|
12578
|
+
console.log(warning(`Warning: ${latestWarning}`));
|
|
12579
|
+
}
|
|
12580
|
+
if (!latestVersion) {
|
|
12581
|
+
return;
|
|
12582
|
+
}
|
|
12583
|
+
const comparison = compareVersions(currentVersion, latestVersion);
|
|
12584
|
+
if (comparison >= 0) {
|
|
12585
|
+
console.log(success(comparison > 0 ? "Current version is newer than npm latest." : "Threadnote is up to date."));
|
|
12586
|
+
console.log("");
|
|
12587
|
+
const whatsNew2 = await withSpinner("Fetching GitHub release notes", () => whatsNewLinesForVersion(currentVersion));
|
|
12588
|
+
printWhatsNew(whatsNew2);
|
|
12589
|
+
return;
|
|
12590
|
+
}
|
|
12591
|
+
console.log("");
|
|
12592
|
+
const whatsNew = await withSpinner(
|
|
12593
|
+
"Fetching GitHub release notes",
|
|
12594
|
+
() => whatsNewLinesForVersionRange(currentVersion, latestVersion)
|
|
12595
|
+
);
|
|
12596
|
+
printWhatsNew(whatsNew);
|
|
12597
|
+
}
|
|
12598
|
+
function printWhatsNew(whatsNew) {
|
|
12599
|
+
for (const line of whatsNew) {
|
|
12600
|
+
console.log(line === "What's new:" ? heading(line) : line);
|
|
12601
|
+
}
|
|
12602
|
+
}
|
|
12603
|
+
|
|
12368
12604
|
// src/threadnote.ts
|
|
12369
12605
|
async function main() {
|
|
12370
12606
|
const program2 = new Command();
|
|
@@ -12390,6 +12626,9 @@ async function main() {
|
|
|
12390
12626
|
}
|
|
12391
12627
|
await maybeNotifyUpdate(config, { dryRun: options.dryRun === true });
|
|
12392
12628
|
});
|
|
12629
|
+
program2.command("version").description("Print the installed Threadnote version, latest npm version, and release notes").option("--registry <url>", "npm registry URL", process.env.THREADNOTE_NPM_REGISTRY).action(async (options) => {
|
|
12630
|
+
await runVersion(getRuntimeConfig(program2), options);
|
|
12631
|
+
});
|
|
12393
12632
|
program2.command("update").description("Update the published Threadnote package, then repair local shims and MCP config").option("--check", "Only check whether a newer version is available").option("--dry-run", "Print update and repair commands without running them").option("--force", "Run package-manager update even if this version is already current").option("--registry <url>", "npm registry URL", process.env.THREADNOTE_NPM_REGISTRY).option("--runtime <runtime>", "auto, npm, bun, or deno", parseUpdateRuntime, "auto").option("--no-repair", "Skip threadnote repair after updating the package").option("--no-post-update", "Skip post-update migration prompts").option("--yes", "Accept applicable post-update migrations without prompting").action(async (options) => {
|
|
12394
12633
|
await runUpdate(getRuntimeConfig(program2), options);
|
|
12395
12634
|
});
|