@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 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
- hint: installed ? reasons.join(" · ") : "not detected",
522
- checked: installed,
523
- group: installed ? "detected" : "other",
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thesmurph/agentlink",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "One source of truth for agent instructions and skills, symlinked into every coding-agent harness.",
5
5
  "keywords": [
6
6
  "agents.md",
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 { selectMany, type Choice } from "./ui.js";
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: Choice[] = detections.map(({ harness, installed, reasons }) => ({
609
- id: harness.id,
610
- label: harness.label,
611
- hint: installed ? reasons.join(" · ") : "not detected",
612
- checked: installed,
613
- group: installed ? "detected" : "other",
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);