@thesmurph/agentlink 0.2.0 → 0.2.1
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/CHANGELOG.md +10 -0
- package/dist/cli.js +8 -9
- package/dist/ui.js +22 -0
- package/package.json +1 -1
- package/src/cli.ts +13 -11
- package/src/ui.ts +26 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.2.1
|
|
4
|
+
|
|
5
|
+
- Fixed: `select` remembered your selection but the picker did not use it, so
|
|
6
|
+
every run re-ticked every installed harness and a harness you removed came
|
|
7
|
+
back by default. The picker now starts from the saved selection, and falls
|
|
8
|
+
back to what is installed only on a first run.
|
|
9
|
+
- The picker's help line says the current selection is pre-ticked, and a
|
|
10
|
+
harness kept in the selection but no longer installed is labelled
|
|
11
|
+
"still linked" rather than looking like a stray checkbox.
|
|
12
|
+
|
|
3
13
|
## 0.2.0
|
|
4
14
|
|
|
5
15
|
Corrects the path table against the code each harness actually ships, checked
|
package/dist/cli.js
CHANGED
|
@@ -11,7 +11,7 @@ import { endpointVerified, HARNESSES, resolveHarnessList } from "./harnesses.js"
|
|
|
11
11
|
import { isIgnoreMode, readIgnoreBlock, removeIgnoreBlock, updateGitignore } from "./ignore.js";
|
|
12
12
|
import { apply, mergeState, plan, pruneStale, readState, unlink, writeState } from "./link.js";
|
|
13
13
|
import { resolveScope } from "./scope.js";
|
|
14
|
-
import { selectMany } from "./ui.js";
|
|
14
|
+
import { buildHarnessChoices, selectMany } from "./ui.js";
|
|
15
15
|
const ESC = String.fromCharCode(27);
|
|
16
16
|
// Colour only when something is reading it: a TTY, and not opted out. Piped
|
|
17
17
|
// output and CI logs stay free of escape codes.
|
|
@@ -502,7 +502,7 @@ async function chooseHarnesses(paths, options, behaviour) {
|
|
|
502
502
|
// Never prompt when the caller cannot answer: --yes, --json, or no TTY.
|
|
503
503
|
const canPrompt = !options.yes && !options.json && process.stdin.isTTY === true && process.stdout.isTTY === true;
|
|
504
504
|
if (behaviour.prompt && canPrompt)
|
|
505
|
-
return promptForHarnesses();
|
|
505
|
+
return promptForHarnesses(paths);
|
|
506
506
|
const fallback = detectedHarnesses();
|
|
507
507
|
if (!options.json) {
|
|
508
508
|
const names = fallback.map((h) => h.id).join(", ") || "none";
|
|
@@ -513,18 +513,17 @@ async function chooseHarnesses(paths, options, behaviour) {
|
|
|
513
513
|
}
|
|
514
514
|
return fallback;
|
|
515
515
|
}
|
|
516
|
-
async function promptForHarnesses() {
|
|
516
|
+
async function promptForHarnesses(paths) {
|
|
517
517
|
const detections = detectAll();
|
|
518
|
-
const choices = detections.map(({ harness, installed, reasons }) => ({
|
|
518
|
+
const choices = buildHarnessChoices(detections.map(({ harness, installed, reasons }) => ({
|
|
519
519
|
id: harness.id,
|
|
520
520
|
label: harness.label,
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
}));
|
|
521
|
+
installed,
|
|
522
|
+
reasons,
|
|
523
|
+
})), readState(paths).harnesses);
|
|
525
524
|
const picked = await selectMany(choices, {
|
|
526
525
|
title: "Which harnesses should read this project's AGENTS.md and skills?",
|
|
527
|
-
help: "space toggle · a all · i installed only · enter confirm · esc cancel",
|
|
526
|
+
help: "current selection pre-ticked · space toggle · a all · i installed only · enter confirm · esc cancel",
|
|
528
527
|
});
|
|
529
528
|
if (picked === null) {
|
|
530
529
|
process.stdout.write(`${DIM}cancelled${RESET}\n`);
|
package/dist/ui.js
CHANGED
|
@@ -6,6 +6,28 @@ const DIM = `${ESC}[2m`;
|
|
|
6
6
|
const BOLD = `${ESC}[1m`;
|
|
7
7
|
const RESET = `${ESC}[0m`;
|
|
8
8
|
const CYAN = `${ESC}[36m`;
|
|
9
|
+
/**
|
|
10
|
+
* What the picker starts with.
|
|
11
|
+
*
|
|
12
|
+
* The saved selection wins, so re-running `select` shows the current state and
|
|
13
|
+
* a harness you removed stays removed. Only a first run, with nothing saved,
|
|
14
|
+
* falls back to what is installed on this machine.
|
|
15
|
+
*/
|
|
16
|
+
export function buildHarnessChoices(inputs, saved) {
|
|
17
|
+
const useSaved = saved.length > 0;
|
|
18
|
+
return inputs.map((input) => {
|
|
19
|
+
const checked = useSaved ? saved.includes(input.id) : input.installed;
|
|
20
|
+
const reasons = input.reasons.length > 0 ? input.reasons.join(" · ") : "not detected";
|
|
21
|
+
return {
|
|
22
|
+
id: input.id,
|
|
23
|
+
label: input.label,
|
|
24
|
+
// A selected-but-absent harness looks like a mistake unless it is explained.
|
|
25
|
+
hint: checked && !input.installed ? `${reasons} · still linked` : reasons,
|
|
26
|
+
checked,
|
|
27
|
+
group: input.installed ? "detected" : "other",
|
|
28
|
+
};
|
|
29
|
+
});
|
|
30
|
+
}
|
|
9
31
|
export async function selectMany(choices, options) {
|
|
10
32
|
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
11
33
|
return choices.filter((c) => c.checked).map((c) => c.id);
|
package/package.json
CHANGED
package/src/cli.ts
CHANGED
|
@@ -11,7 +11,7 @@ import { endpointVerified, HARNESSES, resolveHarnessList, type Harness } from ".
|
|
|
11
11
|
import { isIgnoreMode, readIgnoreBlock, removeIgnoreBlock, updateGitignore, type IgnoreMode } from "./ignore.js";
|
|
12
12
|
import { apply, mergeState, plan, pruneStale, readState, unlink, writeState } from "./link.js";
|
|
13
13
|
import { resolveScope, type Scope, type ScopePaths } from "./scope.js";
|
|
14
|
-
import {
|
|
14
|
+
import { buildHarnessChoices, selectMany } from "./ui.js";
|
|
15
15
|
|
|
16
16
|
const ESC = String.fromCharCode(27);
|
|
17
17
|
// Colour only when something is reading it: a TTY, and not opted out. Piped
|
|
@@ -588,7 +588,7 @@ async function chooseHarnesses(
|
|
|
588
588
|
|
|
589
589
|
// Never prompt when the caller cannot answer: --yes, --json, or no TTY.
|
|
590
590
|
const canPrompt = !options.yes && !options.json && process.stdin.isTTY === true && process.stdout.isTTY === true;
|
|
591
|
-
if (behaviour.prompt && canPrompt) return promptForHarnesses();
|
|
591
|
+
if (behaviour.prompt && canPrompt) return promptForHarnesses(paths);
|
|
592
592
|
|
|
593
593
|
const fallback = detectedHarnesses();
|
|
594
594
|
if (!options.json) {
|
|
@@ -603,19 +603,21 @@ async function chooseHarnesses(
|
|
|
603
603
|
return fallback;
|
|
604
604
|
}
|
|
605
605
|
|
|
606
|
-
async function promptForHarnesses(): Promise<Harness[]> {
|
|
606
|
+
async function promptForHarnesses(paths: ScopePaths): Promise<Harness[]> {
|
|
607
607
|
const detections = detectAll();
|
|
608
|
-
const choices
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
608
|
+
const choices = buildHarnessChoices(
|
|
609
|
+
detections.map(({ harness, installed, reasons }) => ({
|
|
610
|
+
id: harness.id,
|
|
611
|
+
label: harness.label,
|
|
612
|
+
installed,
|
|
613
|
+
reasons,
|
|
614
|
+
})),
|
|
615
|
+
readState(paths).harnesses,
|
|
616
|
+
);
|
|
615
617
|
|
|
616
618
|
const picked = await selectMany(choices, {
|
|
617
619
|
title: "Which harnesses should read this project's AGENTS.md and skills?",
|
|
618
|
-
help: "space toggle · a all · i installed only · enter confirm · esc cancel",
|
|
620
|
+
help: "current selection pre-ticked · space toggle · a all · i installed only · enter confirm · esc cancel",
|
|
619
621
|
});
|
|
620
622
|
if (picked === null) {
|
|
621
623
|
process.stdout.write(`${DIM}cancelled${RESET}\n`);
|
package/src/ui.ts
CHANGED
|
@@ -29,6 +29,32 @@ const BOLD = `${ESC}[1m`;
|
|
|
29
29
|
const RESET = `${ESC}[0m`;
|
|
30
30
|
const CYAN = `${ESC}[36m`;
|
|
31
31
|
|
|
32
|
+
/**
|
|
33
|
+
* What the picker starts with.
|
|
34
|
+
*
|
|
35
|
+
* The saved selection wins, so re-running `select` shows the current state and
|
|
36
|
+
* a harness you removed stays removed. Only a first run, with nothing saved,
|
|
37
|
+
* falls back to what is installed on this machine.
|
|
38
|
+
*/
|
|
39
|
+
export function buildHarnessChoices(
|
|
40
|
+
inputs: { id: string; label: string; installed: boolean; reasons: string[] }[],
|
|
41
|
+
saved: string[],
|
|
42
|
+
): Choice[] {
|
|
43
|
+
const useSaved = saved.length > 0;
|
|
44
|
+
return inputs.map((input) => {
|
|
45
|
+
const checked = useSaved ? saved.includes(input.id) : input.installed;
|
|
46
|
+
const reasons = input.reasons.length > 0 ? input.reasons.join(" · ") : "not detected";
|
|
47
|
+
return {
|
|
48
|
+
id: input.id,
|
|
49
|
+
label: input.label,
|
|
50
|
+
// A selected-but-absent harness looks like a mistake unless it is explained.
|
|
51
|
+
hint: checked && !input.installed ? `${reasons} · still linked` : reasons,
|
|
52
|
+
checked,
|
|
53
|
+
group: input.installed ? "detected" : "other",
|
|
54
|
+
};
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
32
58
|
export async function selectMany(choices: Choice[], options: SelectOptions): Promise<string[] | null> {
|
|
33
59
|
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
34
60
|
return choices.filter((c) => c.checked).map((c) => c.id);
|