@sous-io/sous 0.2.15 → 0.2.16

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.
@@ -41,6 +41,22 @@ Help has four spellings. `sous --help` prints the root screen; `sous repo add --
41
41
  `sous --version` prints the version alone, as `v1.2.3`; `sous --version --verbose` adds the package name, where
42
42
  it is installed, the platform and the Node build under it.
43
43
 
44
+ ## Flags that browse
45
+
46
+ Every command that shows published versions takes the same two flags: `sous recipe list`, `sous recipe show`,
47
+ `sous namespace list`, `sous namespace show`, `sous repo list`, `sous repo search` (and `sous search`) and
48
+ `sous subscription list`. The flags combine.
49
+
50
+ | Flag | What it does |
51
+ |------|--------------|
52
+ | `--latest` | Read each repository's index from upstream instead of the cache. `--remote` is the same flag. What it fetches is never written to the cache; a repository that cannot be reached is shown from the cache and named as not checked |
53
+ | `--installed` | Show only what this project has installed, at the version its lockfile pins. A recipe whose repository is linked is marked `linked`, because builds read it from the checkout rather than the pinned version |
54
+
55
+ Without either flag a browsing command reads only the cached indexes, so it works offline and fast.
56
+ `sous recipe list --installed --latest` is the out-of-date view: each installed recipe with upstream's newest
57
+ version beside the installed one. With `--installed`, `recipe show` and `namespace show` look the reference up
58
+ among installed recipes only, and a reference to something published but not installed is an error saying so.
59
+
44
60
  Every topic answers to both spellings of its name: `repo` and `repos`, `subscription` and `subscriptions`,
45
61
  `namespace` and `namespaces`, `recipe` and `recipes`, `lock` and `locks`, `vars` and `var`, `config` and
46
62
  `configs`. Three commands also answer to one word: `sous search`, `sous subscribe` and `sous unsubscribe`.
@@ -79,6 +95,12 @@ and the command you want almost always. Takes `--dry-run`.
79
95
  - `--strict`: fail on any compilation error rather than reporting it and continuing.
80
96
  - `-w, --watch`: rebuild on every change to a source file, a config layer or a linked checkout.
81
97
 
98
+ Before it compiles, a build lists each recipe this project uses that has a newer version within the range
99
+ declared for it, beside the version pinned. It moves no pin; only always-pull moves one. The build reads upstream
100
+ for this at most once per freshness window (`store.freshnessSeconds`, five minutes by default), gives a
101
+ repository three seconds to answer, and otherwise answers from the cached index without a word about the
102
+ failed check.
103
+
82
104
  Example: `sous build --rebuild`
83
105
 
84
106
  ### `sous compile`
@@ -103,8 +125,10 @@ Example: `sous launch claude --continuous`
103
125
 
104
126
  ### `sous search TEXT`
105
127
  Searches the recipes every trusted repository publishes, by name or description; reads the cached indexes only,
106
- so it works offline. `--limit <n>` sets how many matches to show, defaulting to 25. Also spelled
107
- `sous repo search`. Example: `sous search task --limit 50`
128
+ so it works offline. `--limit <n>` sets how many matches to show, defaulting to 25. Takes the
129
+ [browsing flags](#flags-that-browse): `--latest` searches the indexes upstream serves, and `--installed` searches
130
+ only installed recipes and adds an Installed column. Also spelled `sous repo search`.
131
+ Example: `sous search task --limit 50`
108
132
 
109
133
  ### `sous help [COMMAND]`
110
134
  Prints the help for sous, or for one command or topic. Works from any directory, including one with no config
@@ -155,7 +179,9 @@ held, the files the next build prunes, and the linked checkout if one points at
155
179
  ### `sous repo list`
156
180
  Lists the repositories this project trusts, with the provider, where the entry came from, whether it is linked,
157
181
  how many recipes it publishes (`not fetched` until its index has been downloaded) and its URL. `--verbose` adds
158
- the namespaces each one publishes, on a line under its row.
182
+ the namespaces each one publishes, on a line under its row. Takes the [browsing flags](#flags-that-browse):
183
+ `--installed` keeps only the repositories something is installed from and names each installed recipe and
184
+ version on a line under its row.
159
185
 
160
186
  ```term
161
187
  $ sous repo list
@@ -165,7 +191,8 @@ $ sous repo list
165
191
  ```
166
192
 
167
193
  ### `sous repo search TEXT`
168
- Same command as `sous search`, under its own topic; takes `--limit <n>`. Example: `sous repo search browser`
194
+ Same command as `sous search`, under its own topic; takes `--limit <n>` and the
195
+ [browsing flags](#flags-that-browse). Example: `sous repo search browser --installed`
169
196
 
170
197
  ### `sous repo gc`
171
198
  Collects the machine-wide recipe store down to its size cap. `--max-bytes <n>` collects to that cap instead of
@@ -265,13 +292,16 @@ Removes a subscription and everything only it brought in, then rebuilds so those
265
292
 
266
293
  ### `sous subscription list`
267
294
  Lists the subscriptions this project declares, switched-off ones included, with the range each resolves within,
268
- the versions the lockfile pins, where it came from and whether it is on. Reads the config and the lockfile only.
269
- Example: `sous subscription list`
295
+ the versions the lockfile pins, the latest version each of those recipes has published, where it came from and
296
+ whether it is on. Reads the config, the lockfile and the cached indexes only. Takes the
297
+ [browsing flags](#flags-that-browse): `--latest` reads the latest versions from upstream, and `--installed` keeps
298
+ only the subscriptions that have pinned something. Example: `sous subscription list --latest`
270
299
 
271
300
  ## namespace
272
301
 
273
302
  `namespace` reads the cached indexes and the lockfile, so it works offline. A trusted repository whose index has
274
- never been fetched is named at the end of a listing, not left out.
303
+ never been fetched is named at the end of a listing, not left out. Both commands take the
304
+ [browsing flags](#flags-that-browse); with `--installed` the recipe count is the number installed.
275
305
 
276
306
  ### `sous namespace list`
277
307
  Lists every namespace the trusted repositories publish, how many recipes each holds, and how much of it this
@@ -284,11 +314,12 @@ Example: `sous namespace show sous-recipes:core`
284
314
 
285
315
  ## recipe
286
316
 
287
- Browses the recipes the trusted repositories publish; like `namespace`, it works offline.
317
+ Browses the recipes the trusted repositories publish; like `namespace`, it works offline, and both commands take
318
+ the [browsing flags](#flags-that-browse).
288
319
 
289
320
  ### `sous recipe list`
290
321
  Lists the recipes the trusted repositories publish, across every namespace, with the same per-recipe columns
291
- `namespace show` prints. Example: `sous recipe list`
322
+ `namespace show` prints. Example: `sous recipe list --installed --latest`
292
323
 
293
324
  ### `sous recipe show REF`
294
325
  Describes one recipe completely: its repository and location, every published version, its dependencies as
@@ -52,6 +52,19 @@ sous namespace show workflow # one namespace and the recipes in it
52
52
  sous recipe list # every recipe, latest version, pinned version, subscribed
53
53
  ```
54
54
 
55
+ The cache can be behind what a repository has published since. Every one of these commands, plus
56
+ `sous recipe show`, `sous repo list` and `sous subscription list`, takes two flags that combine:
57
+
58
+ - `--latest` (also `--remote`) reads each index from upstream instead. Nothing it fetches is
59
+ written to the cache; only a command that resolves versions changes what the cache holds. A
60
+ repository that cannot be reached is shown from the cache and named as not checked.
61
+ - `--installed` narrows the listing to what this project has installed, at the version the
62
+ lockfile pins. A recipe from a linked repository is marked `linked`, because builds read it from
63
+ the checkout instead of that version.
64
+
65
+ `sous recipe list --installed --latest` is the out-of-date view: every installed recipe, with
66
+ upstream's newest version beside the installed one.
67
+
55
68
  Read `sous recipe show` before subscribing: every published version, what it depends on (as
56
69
  declared, beside the version the index resolved it to), and its questions and files once it has
57
70
  them here.
@@ -201,7 +214,8 @@ $ sous subscription list
201
214
 
202
215
  A namespace subscription names every recipe it holds, each with the version the lockfile pins. A
203
216
  subscription that has never been built has nothing pinned yet, and its cell reads `pinned on first
204
- build` instead.
217
+ build` instead. The `Latest version` column beside it is read from the cache, or from upstream with
218
+ `--latest`.
205
219
 
206
220
  `sous repo list` shows each trusted repository with its provider, origin, whether it is linked, its
207
221
  recipe count and its URL; `--verbose` adds a `Namespaces:` line under each row. `sous lock show`
@@ -278,6 +292,23 @@ happens after that check, not how often it happens: a repository or subscription
278
292
  `alwaysPull` takes a newer in-range version rather than the locked one; set it with
279
293
  `--always-pull`, or on either entry in the config.
280
294
 
295
+ Every other repository the lockfile pins from is checked on the same window, only to tell you what
296
+ is newer. A build lists each recipe with a newer version inside the range declared for it, beside
297
+ the version it pins, and moves nothing:
298
+
299
+ ```term
300
+ $ sous build
301
+ ▶ Newer versions published:
302
+
303
+ workflow/qa-variables: 0.2.0 this project pins 0.1.0
304
+
305
+ This version is within the range declared for the recipe. No pin was changed, so this build
306
+ uses the pinned version.
307
+ ```
308
+
309
+ That check waits at most three seconds for a repository, and a check that fails or runs out of
310
+ time is not mentioned: the cached index answers instead, and the build carries on.
311
+
281
312
  The store is machine-wide and disposable, because everything in it is re-fetchable from a
282
313
  lockfile's pins. `sous repo gc` collects it back to its size cap, evicting least recently used
283
314
  entries first, and protects everything this project's lockfile pins whatever that does to the
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sous-io/sous",
3
- "version": "0.2.15",
3
+ "version": "0.2.16",
4
4
  "description": "Compiles AI coding agent configuration (CLAUDE.md, skills, memories) from LiquidJS templates",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -11,7 +11,7 @@ formatVersion: 1
11
11
 
12
12
  namespace: core
13
13
  name: sous-skills
14
- version: 0.2.15
14
+ version: 0.2.16
15
15
 
16
16
  description: >-
17
17
  The skills that teach an agent what sous is and how it works: which files sous
@@ -3,17 +3,25 @@
3
3
  *
4
4
  * Shows every namespace published by the repositories this project trusts, how
5
5
  * many recipes each one holds, and how much of it the project subscribes to. It
6
- * reads only the indexes sous already has on disk, so it works offline; a
7
- * repository whose index has never been fetched is named at the end rather than
8
- * being silently left out.
6
+ * reads only the indexes sous already has on disk by default, so it works
7
+ * offline; a repository whose index has never been fetched is named at the end
8
+ * rather than being silently left out. `--latest` reads the indexes from
9
+ * upstream instead, without saving them, and `--installed` narrows the listing
10
+ * to the namespaces the lockfile pins a recipe from.
9
11
  */
10
12
 
11
13
  import { BaseCommand } from "../../base-command.js";
12
14
  import { subscriptionServiceFor } from "../../lib/repos/subscription-service.js";
13
- import { catalogContextFor } from "../../lib/repos/catalog-inputs.js";
14
- import { listNamespaces } from "../../lib/repos/catalog.js";
15
- import { INDENT, describeCoverage } from "../../lib/repos/catalog-display.js";
15
+ import { loadCatalogContext } from "../../lib/repos/catalog-inputs.js";
16
+ import { listNamespaces, narrowToInstalled } from "../../lib/repos/catalog.js";
17
+ import {
18
+ INDENT,
19
+ describeCoverage,
20
+ describeIndexSource,
21
+ printBrowsingNotes,
22
+ } from "../../lib/repos/catalog-display.js";
16
23
  import { renderTable, type TableColumn } from "../../utils/table.js";
24
+ import { browsingFlags } from "../../utils/flags.js";
17
25
  import {
18
26
  blankLine,
19
27
  footer,
@@ -53,19 +61,28 @@ export default class NamespaceList extends BaseCommand {
53
61
  */
54
62
  static aliases = ["namespaces:list"];
55
63
 
56
- static examples = ["<%= config.bin %> namespace list"];
64
+ static examples = [
65
+ "<%= config.bin %> namespace list",
66
+ "<%= config.bin %> namespace list --installed",
67
+ ];
57
68
 
58
- static flags = { ...BaseCommand.baseFlags };
69
+ static flags = { ...BaseCommand.baseFlags, ...browsingFlags() };
59
70
 
60
71
  async run(): Promise<void> {
61
- await this.parse(NamespaceList);
72
+ const { flags } = await this.parse(NamespaceList);
62
73
 
63
74
  showCommandVars({
64
75
  Project: this.projectLabel,
65
76
  Config: this.configContext.configPath,
77
+ Reading: describeIndexSource(flags.latest),
78
+ ...(flags.installed ? { Showing: "only what this project has installed" } : {}),
66
79
  });
67
80
 
68
- heading("Namespaces in the repositories this project trusts");
81
+ heading(
82
+ flags.installed
83
+ ? "Namespaces this project has installed recipes from"
84
+ : "Namespaces in the repositories this project trusts"
85
+ );
69
86
 
70
87
  const service = subscriptionServiceFor({
71
88
  configContext: this.configContext,
@@ -73,13 +90,21 @@ export default class NamespaceList extends BaseCommand {
73
90
  shellEnv: this.shellEnv,
74
91
  });
75
92
 
76
- const { inputs, notFetched } = catalogContextFor({
93
+ const { inputs, notFetched, notChecked } = await loadCatalogContext({
77
94
  service,
78
95
  sousDir: this.configContext.sousDir,
79
96
  settings: this.settings,
97
+ latest: flags.latest,
80
98
  });
81
99
 
82
- const listings = listNamespaces(inputs);
100
+ // Narrowed to what is installed, a namespace's recipe count is the number
101
+ // of its recipes the project has installed, and the column says so.
102
+ const listings = listNamespaces(flags.installed ? narrowToInstalled(inputs) : inputs);
103
+ const columns = flags.installed
104
+ ? COLUMNS.map((column) =>
105
+ column.key === "recipes" ? { ...column, header: "Installed" } : column
106
+ )
107
+ : COLUMNS;
83
108
 
84
109
  blankLine();
85
110
 
@@ -88,7 +113,9 @@ export default class NamespaceList extends BaseCommand {
88
113
  inputs.repos.length === 0
89
114
  ? "Sous has read no repository index for this project, so there are no " +
90
115
  "namespaces to show."
91
- : "The repositories this project trusts publish no namespaces."
116
+ : flags.installed
117
+ ? "This project has installed no recipe from the repositories it trusts."
118
+ : "The repositories this project trusts publish no namespaces."
92
119
  );
93
120
  } else {
94
121
  const rows = listings.map((entry) => ({
@@ -99,18 +126,12 @@ export default class NamespaceList extends BaseCommand {
99
126
  description: entry.description ?? "no description published",
100
127
  }));
101
128
 
102
- for (const line of renderTable(COLUMNS, rows, { indent: INDENT })) {
129
+ for (const line of renderTable(columns, rows, { indent: INDENT })) {
103
130
  log(indent(line, INDENT));
104
131
  }
105
132
  }
106
133
 
107
- if (notFetched.length > 0) {
108
- blankLine();
109
- paragraph(
110
- `These repositories are trusted and their index has not been fetched yet, so ` +
111
- `nothing in them is listed: ${notFetched.join(", ")}.`
112
- );
113
- }
134
+ printBrowsingNotes({ notFetched, notChecked });
114
135
 
115
136
  footer();
116
137
  }
@@ -4,23 +4,28 @@
4
4
  * Shows one namespace in full: the repository publishing it, what that
5
5
  * repository says it is for, how much of it this project subscribes to, and
6
6
  * every recipe in it with its latest version, its pinned version and its own
7
- * subscription state. It reads only the indexes sous already has on disk.
7
+ * subscription state. It reads only the indexes sous already has on disk,
8
+ * unless `--latest` asks it to read upstream (which saves nothing); `--installed`
9
+ * narrows the recipes to the ones the lockfile pins.
8
10
  */
9
11
 
10
12
  import { Args } from "@oclif/core";
11
13
  import { BaseCommand } from "../../base-command.js";
12
14
  import { subscriptionServiceFor } from "../../lib/repos/subscription-service.js";
13
- import { catalogContextFor } from "../../lib/repos/catalog-inputs.js";
14
- import { describeNamespace } from "../../lib/repos/catalog.js";
15
+ import { loadCatalogContext } from "../../lib/repos/catalog-inputs.js";
16
+ import { describeInstalled, describeNamespace } from "../../lib/repos/catalog.js";
15
17
  import {
16
18
  INDENT,
17
- RECIPE_COLUMNS,
18
19
  describeCoverage,
20
+ describeIndexSource,
19
21
  factIf,
22
+ printBrowsingNotes,
20
23
  printFacts,
24
+ recipeColumns,
21
25
  recipeRows,
22
26
  } from "../../lib/repos/catalog-display.js";
23
27
  import { renderTable } from "../../utils/table.js";
28
+ import { browsingFlags } from "../../utils/flags.js";
24
29
  import {
25
30
  blankLine,
26
31
  footer,
@@ -53,15 +58,17 @@ export default class NamespaceShow extends BaseCommand {
53
58
  }),
54
59
  };
55
60
 
56
- static flags = { ...BaseCommand.baseFlags };
61
+ static flags = { ...BaseCommand.baseFlags, ...browsingFlags() };
57
62
 
58
63
  async run(): Promise<void> {
59
- const { args } = await this.parse(NamespaceShow);
64
+ const { args, flags } = await this.parse(NamespaceShow);
60
65
 
61
66
  showCommandVars({
62
67
  Project: this.projectLabel,
63
68
  Config: this.configContext.configPath,
64
69
  Namespace: args.ref,
70
+ Reading: describeIndexSource(flags.latest),
71
+ ...(flags.installed ? { Showing: "only what this project has installed" } : {}),
65
72
  });
66
73
 
67
74
  const service = subscriptionServiceFor({
@@ -70,13 +77,16 @@ export default class NamespaceShow extends BaseCommand {
70
77
  shellEnv: this.shellEnv,
71
78
  });
72
79
 
73
- const { inputs } = catalogContextFor({
80
+ const { inputs, notChecked } = await loadCatalogContext({
74
81
  service,
75
82
  sousDir: this.configContext.sousDir,
76
83
  settings: this.settings,
84
+ latest: flags.latest,
77
85
  });
78
86
 
79
- const detail = describeNamespace(inputs, args.ref);
87
+ const detail = flags.installed
88
+ ? describeInstalled(inputs, args.ref, describeNamespace, "namespace")
89
+ : describeNamespace(inputs, args.ref);
80
90
 
81
91
  heading(`The namespace ${detail.namespace}`);
82
92
  blankLine();
@@ -85,7 +95,10 @@ export default class NamespaceShow extends BaseCommand {
85
95
  { label: "Repository", lines: [detail.repo] },
86
96
  ...factIf("Location", detail.repoUrl),
87
97
  ...factIf("About", detail.description),
88
- { label: "Recipes", lines: [String(detail.recipes.length)] },
98
+ {
99
+ label: flags.installed ? "Installed recipes" : "Recipes",
100
+ lines: [String(detail.recipes.length)],
101
+ },
89
102
  { label: "Subscribed", lines: [describeCoverage(detail.subscribed)] },
90
103
  ]);
91
104
 
@@ -99,12 +112,19 @@ export default class NamespaceShow extends BaseCommand {
99
112
  return;
100
113
  }
101
114
 
102
- for (const line of renderTable(RECIPE_COLUMNS, recipeRows(detail.recipes), {
103
- indent: INDENT,
104
- })) {
115
+ for (const line of renderTable(
116
+ recipeColumns({ installed: flags.installed }),
117
+ recipeRows(detail.recipes),
118
+ { indent: INDENT }
119
+ )) {
105
120
  log(indent(line, INDENT));
106
121
  }
107
122
 
123
+ printBrowsingNotes({
124
+ notChecked: notChecked.filter((name) => name === detail.repo),
125
+ anyLinked: detail.recipes.some((entry) => entry.linkedPath !== undefined),
126
+ });
127
+
108
128
  footer();
109
129
  }
110
130
  }
@@ -3,21 +3,26 @@
3
3
  *
4
4
  * Shows every recipe the repositories this project trusts publish, with the
5
5
  * latest published version, the version this project pins, and whether it is
6
- * subscribed. It reads only the indexes sous already has on disk, so it works
7
- * offline; a repository whose index has never been fetched is named at the end
8
- * rather than being silently left out.
6
+ * subscribed. By default it reads only the indexes sous already has on disk, so
7
+ * it works offline; a repository whose index has never been fetched is named at
8
+ * the end rather than being silently left out. `--latest` reads the indexes
9
+ * from upstream instead, without saving them, and `--installed` narrows the
10
+ * listing to what the lockfile pins.
9
11
  */
10
12
 
11
13
  import { BaseCommand } from "../../base-command.js";
12
14
  import { subscriptionServiceFor } from "../../lib/repos/subscription-service.js";
13
- import { catalogContextFor } from "../../lib/repos/catalog-inputs.js";
14
- import { listRecipes } from "../../lib/repos/catalog.js";
15
+ import { loadCatalogContext } from "../../lib/repos/catalog-inputs.js";
16
+ import { listRecipes, narrowToInstalled } from "../../lib/repos/catalog.js";
15
17
  import {
16
18
  INDENT,
17
- RECIPE_COLUMNS,
19
+ describeIndexSource,
20
+ printBrowsingNotes,
21
+ recipeColumns,
18
22
  recipeRows,
19
23
  } from "../../lib/repos/catalog-display.js";
20
24
  import { renderTable } from "../../utils/table.js";
25
+ import { browsingFlags } from "../../utils/flags.js";
21
26
  import {
22
27
  blankLine,
23
28
  footer,
@@ -37,19 +42,28 @@ export default class RecipeList extends BaseCommand {
37
42
  */
38
43
  static aliases = ["recipes:list"];
39
44
 
40
- static examples = ["<%= config.bin %> recipe list"];
45
+ static examples = [
46
+ "<%= config.bin %> recipe list",
47
+ "<%= config.bin %> recipe list --installed --latest",
48
+ ];
41
49
 
42
- static flags = { ...BaseCommand.baseFlags };
50
+ static flags = { ...BaseCommand.baseFlags, ...browsingFlags() };
43
51
 
44
52
  async run(): Promise<void> {
45
- await this.parse(RecipeList);
53
+ const { flags } = await this.parse(RecipeList);
46
54
 
47
55
  showCommandVars({
48
56
  Project: this.projectLabel,
49
57
  Config: this.configContext.configPath,
58
+ Reading: describeIndexSource(flags.latest),
59
+ ...(flags.installed ? { Showing: "only what this project has installed" } : {}),
50
60
  });
51
61
 
52
- heading("Recipes in the repositories this project trusts");
62
+ heading(
63
+ flags.installed
64
+ ? "Recipes this project has installed"
65
+ : "Recipes in the repositories this project trusts"
66
+ );
53
67
 
54
68
  const service = subscriptionServiceFor({
55
69
  configContext: this.configContext,
@@ -57,13 +71,14 @@ export default class RecipeList extends BaseCommand {
57
71
  shellEnv: this.shellEnv,
58
72
  });
59
73
 
60
- const { inputs, notFetched } = catalogContextFor({
74
+ const { inputs, notFetched, notChecked } = await loadCatalogContext({
61
75
  service,
62
76
  sousDir: this.configContext.sousDir,
63
77
  settings: this.settings,
78
+ latest: flags.latest,
64
79
  });
65
80
 
66
- const listings = listRecipes(inputs);
81
+ const listings = listRecipes(flags.installed ? narrowToInstalled(inputs) : inputs);
67
82
 
68
83
  blankLine();
69
84
 
@@ -72,23 +87,25 @@ export default class RecipeList extends BaseCommand {
72
87
  inputs.repos.length === 0
73
88
  ? "Sous has read no repository index for this project, so there are no " +
74
89
  "recipes to show."
75
- : "The repositories this project trusts publish no recipes."
90
+ : flags.installed
91
+ ? "This project has installed no recipe from the repositories it trusts."
92
+ : "The repositories this project trusts publish no recipes."
76
93
  );
77
94
  } else {
78
- for (const line of renderTable(RECIPE_COLUMNS, recipeRows(listings), {
79
- indent: INDENT,
80
- })) {
95
+ for (const line of renderTable(
96
+ recipeColumns({ installed: flags.installed }),
97
+ recipeRows(listings),
98
+ { indent: INDENT }
99
+ )) {
81
100
  log(indent(line, INDENT));
82
101
  }
83
102
  }
84
103
 
85
- if (notFetched.length > 0) {
86
- blankLine();
87
- paragraph(
88
- `These repositories are trusted and their index has not been fetched yet, so ` +
89
- `nothing in them is listed: ${notFetched.join(", ")}.`
90
- );
91
- }
104
+ printBrowsingNotes({
105
+ notFetched,
106
+ notChecked,
107
+ anyLinked: listings.some((entry) => entry.linkedPath !== undefined),
108
+ });
92
109
 
93
110
  footer();
94
111
  }
@@ -6,14 +6,17 @@
6
6
  * and where its files land in this project. It reads only what sous already has
7
7
  * on disk: the repository's cached index, the project's lockfile, and the
8
8
  * recipe's own files when they are in the store or a linked working copy.
9
+ * `--latest` reads the repository's index from upstream instead, saving
10
+ * nothing, and `--installed` looks the ref up among installed recipes only.
9
11
  */
10
12
 
11
13
  import { Args } from "@oclif/core";
12
14
  import { BaseCommand } from "../../base-command.js";
13
15
  import { resolveRootScope } from "../../lib/settings.js";
14
16
  import { subscriptionServiceFor } from "../../lib/repos/subscription-service.js";
15
- import { catalogContextFor } from "../../lib/repos/catalog-inputs.js";
17
+ import { loadCatalogContext } from "../../lib/repos/catalog-inputs.js";
16
18
  import {
19
+ describeInstalled,
17
20
  describeRecipe,
18
21
  type RecipeContentListing,
19
22
  type RecipeDependencyListing,
@@ -22,11 +25,14 @@ import {
22
25
  } from "../../lib/repos/catalog.js";
23
26
  import {
24
27
  INDENT,
28
+ describeIndexSource,
25
29
  describeVersionStatus,
26
30
  factIf,
31
+ printBrowsingNotes,
27
32
  printFacts,
28
33
  } from "../../lib/repos/catalog-display.js";
29
34
  import { renderTable, type TableColumn } from "../../utils/table.js";
35
+ import { browsingFlags } from "../../utils/flags.js";
30
36
  import {
31
37
  blankLine,
32
38
  footer,
@@ -102,15 +108,17 @@ export default class RecipeShow extends BaseCommand {
102
108
  }),
103
109
  };
104
110
 
105
- static flags = { ...BaseCommand.baseFlags };
111
+ static flags = { ...BaseCommand.baseFlags, ...browsingFlags() };
106
112
 
107
113
  async run(): Promise<void> {
108
- const { args } = await this.parse(RecipeShow);
114
+ const { args, flags } = await this.parse(RecipeShow);
109
115
 
110
116
  showCommandVars({
111
117
  Project: this.projectLabel,
112
118
  Config: this.configContext.configPath,
113
119
  Recipe: args.ref,
120
+ Reading: describeIndexSource(flags.latest),
121
+ ...(flags.installed ? { Showing: "only what this project has installed" } : {}),
114
122
  });
115
123
 
116
124
  const service = subscriptionServiceFor({
@@ -119,14 +127,17 @@ export default class RecipeShow extends BaseCommand {
119
127
  shellEnv: this.shellEnv,
120
128
  });
121
129
 
122
- const { inputs } = catalogContextFor({
130
+ const { inputs, notChecked } = await loadCatalogContext({
123
131
  service,
124
132
  sousDir: this.configContext.sousDir,
125
133
  settings: this.settings,
126
134
  scope: resolveRootScope(this.settings, this.configContext),
135
+ latest: flags.latest,
127
136
  });
128
137
 
129
- const detail = describeRecipe(inputs, args.ref);
138
+ const detail = flags.installed
139
+ ? describeInstalled(inputs, args.ref, describeRecipe, "recipe")
140
+ : describeRecipe(inputs, args.ref);
130
141
 
131
142
  heading(detail.key);
132
143
  blankLine();
@@ -137,11 +148,22 @@ export default class RecipeShow extends BaseCommand {
137
148
  ...factIf("About", detail.description),
138
149
  { label: "Folder", lines: [detail.path] },
139
150
  { label: "Latest version", lines: [detail.latest ?? "none published"] },
140
- { label: "Pinned version", lines: [detail.pinned ?? "this project pins none"] },
151
+ {
152
+ label: flags.installed ? "Installed version" : "Pinned version",
153
+ lines: [detail.pinned ?? "this project pins none"],
154
+ },
155
+ ...factIf(
156
+ "Linked",
157
+ detail.linkedPath === undefined
158
+ ? undefined
159
+ : `builds currently read this recipe from the checkout at ${detail.linkedPath}`
160
+ ),
141
161
  { label: "Subscribed", lines: [detail.subscribed ? "yes" : "no"] },
142
162
  ...factIf("Described below", detail.describing),
143
163
  ]);
144
164
 
165
+ printBrowsingNotes({ notChecked: notChecked.filter((name) => name === detail.repo) });
166
+
145
167
  this.printVersions(detail.versions);
146
168
  this.printDependencies(detail.dependencies, detail.manifestRead);
147
169