@aliou/pi-ts-aperture 0.0.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/.github/workflows/publish.yml +4 -8
  2. package/CHANGELOG.md +28 -0
  3. package/README.md +1 -1
  4. package/package.json +19 -18
  5. package/src/commands/settings.ts +7 -4
  6. package/src/commands/setup.ts +134 -13
  7. package/src/index.ts +56 -18
  8. package/src/lib/health.ts +30 -0
  9. package/.envrc +0 -21
  10. package/.osgrep/cache/meta.lmdb +0 -0
  11. package/.osgrep/cache/meta.lmdb-lock +0 -0
  12. package/.osgrep/lancedb/chunks.lance/_indices/b9016ebf-b6e6-4c43-be9f-7413728f6389/metadata.lance +0 -0
  13. package/.osgrep/lancedb/chunks.lance/_indices/b9016ebf-b6e6-4c43-be9f-7413728f6389/part_11_docs.lance +0 -0
  14. package/.osgrep/lancedb/chunks.lance/_indices/b9016ebf-b6e6-4c43-be9f-7413728f6389/part_11_invert.lance +0 -0
  15. package/.osgrep/lancedb/chunks.lance/_indices/b9016ebf-b6e6-4c43-be9f-7413728f6389/part_11_tokens.lance +0 -0
  16. package/.osgrep/lancedb/chunks.lance/_indices/cc7bd185-bc88-4dbd-af7f-6aac2f6b29d3/metadata.lance +0 -0
  17. package/.osgrep/lancedb/chunks.lance/_indices/cc7bd185-bc88-4dbd-af7f-6aac2f6b29d3/part_0_docs.lance +0 -0
  18. package/.osgrep/lancedb/chunks.lance/_indices/cc7bd185-bc88-4dbd-af7f-6aac2f6b29d3/part_0_invert.lance +0 -0
  19. package/.osgrep/lancedb/chunks.lance/_indices/cc7bd185-bc88-4dbd-af7f-6aac2f6b29d3/part_0_tokens.lance +0 -0
  20. package/.osgrep/lancedb/chunks.lance/_indices/cd01c8d9-fae8-4482-b01f-59f06abe1b48/metadata.lance +0 -0
  21. package/.osgrep/lancedb/chunks.lance/_indices/cd01c8d9-fae8-4482-b01f-59f06abe1b48/part_0_docs.lance +0 -0
  22. package/.osgrep/lancedb/chunks.lance/_indices/cd01c8d9-fae8-4482-b01f-59f06abe1b48/part_0_invert.lance +0 -0
  23. package/.osgrep/lancedb/chunks.lance/_indices/cd01c8d9-fae8-4482-b01f-59f06abe1b48/part_0_tokens.lance +0 -0
  24. package/.osgrep/lancedb/chunks.lance/_indices/dedba042-4a7b-44d7-ac2f-258233b62681/metadata.lance +0 -0
  25. package/.osgrep/lancedb/chunks.lance/_indices/dedba042-4a7b-44d7-ac2f-258233b62681/part_0_docs.lance +0 -0
  26. package/.osgrep/lancedb/chunks.lance/_indices/dedba042-4a7b-44d7-ac2f-258233b62681/part_0_invert.lance +0 -0
  27. package/.osgrep/lancedb/chunks.lance/_indices/dedba042-4a7b-44d7-ac2f-258233b62681/part_0_tokens.lance +0 -0
  28. package/.osgrep/lancedb/chunks.lance/_transactions/0-fd59fbcc-e229-4069-b8b0-764b34f2d645.txn +0 -0
  29. package/.osgrep/lancedb/chunks.lance/_transactions/1-f130f13d-123c-4e71-b806-b6b4f6cdd6d8.txn +0 -0
  30. package/.osgrep/lancedb/chunks.lance/_transactions/2-5f641c2a-07a9-45a1-b450-72c7d4d38754.txn +0 -1
  31. package/.osgrep/lancedb/chunks.lance/_transactions/3-83167775-6ec4-4cb6-9388-6549f583087a.txn +0 -0
  32. package/.osgrep/lancedb/chunks.lance/_transactions/4-feb1627d-f860-4c02-b8a6-6b93cf42ec22.txn +0 -0
  33. package/.osgrep/lancedb/chunks.lance/_transactions/5-e00ef82b-90b4-4c91-a7ec-d7b4903e1c61.txn +0 -0
  34. package/.osgrep/lancedb/chunks.lance/_transactions/6-ccc1d487-f511-4e48-afa0-2edf42f8effd.txn +0 -0
  35. package/.osgrep/lancedb/chunks.lance/_transactions/7-b26d7d18-4411-44cd-b559-54bfa2999b70.txn +0 -0
  36. package/.osgrep/lancedb/chunks.lance/_versions/1.manifest +0 -0
  37. package/.osgrep/lancedb/chunks.lance/_versions/2.manifest +0 -0
  38. package/.osgrep/lancedb/chunks.lance/_versions/3.manifest +0 -0
  39. package/.osgrep/lancedb/chunks.lance/_versions/4.manifest +0 -0
  40. package/.osgrep/lancedb/chunks.lance/_versions/5.manifest +0 -0
  41. package/.osgrep/lancedb/chunks.lance/_versions/6.manifest +0 -0
  42. package/.osgrep/lancedb/chunks.lance/_versions/7.manifest +0 -0
  43. package/.osgrep/lancedb/chunks.lance/_versions/8.manifest +0 -0
  44. package/.osgrep/lancedb/chunks.lance/data/0110110100011111000100108ca476475fac277950ba4295de.lance +0 -0
  45. package/.osgrep/lancedb/chunks.lance/data/111110011111110001101001d059cb4ceab8147482b4391458.lance +0 -0
@@ -57,14 +57,10 @@ jobs:
57
57
 
58
58
  let title = 'Version Packages';
59
59
  let commit = 'Version Packages';
60
- if (releases.length === 1) {
61
- const { name, newVersion } = releases[0];
62
- title = 'Updating ' + name + ' to version ' + newVersion;
63
- commit = name + '@' + newVersion;
64
- } else if (releases.length > 1) {
65
- const summary = releases.map(r => r.name + '@' + r.newVersion).join(', ');
66
- title = 'Updating ' + summary;
67
- commit = summary;
60
+ if (releases.length >= 1) {
61
+ const version = releases[0].newVersion;
62
+ title = version;
63
+ commit = version;
68
64
  }
69
65
 
70
66
  fs.appendFileSync(process.env.GITHUB_OUTPUT, 'title=' + title + '\n');
package/CHANGELOG.md ADDED
@@ -0,0 +1,28 @@
1
+ # @aliou/pi-ts-aperture
2
+
3
+ ## 0.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 926f0a9: Improve `/aperture:setup` provider and connectivity flow.
8
+
9
+ - Add URL health check during setup (`/v1/models`) before provider selection, with retry/cancel UX.
10
+ - Build provider choices from Pi's runtime model registry so extension-registered providers (for example `pi-synthetic`) appear in the setup list.
11
+
12
+ ### Patch Changes
13
+
14
+ - 2263fc2: mark pi SDK peer deps as optional to prevent koffi OOM in Gondolin VMs
15
+
16
+ ## 0.1.0
17
+
18
+ ### Minor Changes
19
+
20
+ - ebb9556: Initial release. Route Pi LLM providers through Tailscale Aperture.
21
+
22
+ - `/aperture:setup` interactive wizard (base URL + provider multi-select)
23
+ - `/aperture:settings` settings UI for updating configuration
24
+ - Auto-registers selected providers with Aperture base URL on load
25
+
26
+ ### Patch Changes
27
+
28
+ - 7388139: Fix providers not taking effect immediately after setup/settings save. Register directly on modelRegistry and re-resolve the active model when it belongs to a reconfigured provider.
package/README.md CHANGED
@@ -7,7 +7,7 @@ Aperture handles API key injection and request routing server-side. This extensi
7
7
  ## Setup
8
8
 
9
9
  ```bash
10
- pi install @aliou/pi-ts-aperture
10
+ pi install npm:@aliou/pi-ts-aperture
11
11
  ```
12
12
 
13
13
  Then run the setup wizard:
package/package.json CHANGED
@@ -1,8 +1,7 @@
1
1
  {
2
2
  "name": "@aliou/pi-ts-aperture",
3
3
  "description": "Route Pi LLM providers through Tailscale Aperture",
4
- "version": "0.0.1",
5
- "packageManager": "pnpm@10.26.1",
4
+ "version": "0.2.0",
6
5
  "repository": {
7
6
  "type": "git",
8
7
  "url": "https://github.com/aliou/pi-ts-aperture"
@@ -11,7 +10,8 @@
11
10
  "pi-package"
12
11
  ],
13
12
  "publishConfig": {
14
- "access": "public"
13
+ "access": "public",
14
+ "provenance": true
15
15
  },
16
16
  "pi": {
17
17
  "extensions": [
@@ -20,21 +20,12 @@
20
20
  "video": "https://assets.aliou.me/pi-extensions/demos/pi-ts-aperture.mp4"
21
21
  },
22
22
  "dependencies": {
23
- "@aliou/pi-utils-settings": "^0.3.0"
23
+ "@aliou/pi-utils-settings": "^0.4.0"
24
24
  },
25
25
  "peerDependencies": {
26
26
  "@mariozechner/pi-ai": ">=0.52.12",
27
27
  "@mariozechner/pi-coding-agent": ">=0.52.12"
28
28
  },
29
- "scripts": {
30
- "typecheck": "tsc --noEmit",
31
- "lint": "biome check",
32
- "format": "biome check --write",
33
- "prepare": "husky",
34
- "changeset": "changeset",
35
- "version": "changeset version",
36
- "release": "pnpm changeset publish"
37
- },
38
29
  "devDependencies": {
39
30
  "@biomejs/biome": "^2.3.13",
40
31
  "@changesets/cli": "^2.27.11",
@@ -45,10 +36,20 @@
45
36
  "husky": "^9.1.7",
46
37
  "typescript": "^5.9.3"
47
38
  },
48
- "pnpm": {
49
- "overrides": {
50
- "@mariozechner/pi-ai": "$@mariozechner/pi-coding-agent",
51
- "@mariozechner/pi-tui": "$@mariozechner/pi-coding-agent"
39
+ "peerDependenciesMeta": {
40
+ "@mariozechner/pi-coding-agent": {
41
+ "optional": true
42
+ },
43
+ "@mariozechner/pi-ai": {
44
+ "optional": true
52
45
  }
46
+ },
47
+ "scripts": {
48
+ "typecheck": "tsc --noEmit",
49
+ "lint": "biome check",
50
+ "format": "biome check --write",
51
+ "changeset": "changeset",
52
+ "version": "changeset version",
53
+ "release": "pnpm changeset publish"
53
54
  }
54
- }
55
+ }
@@ -12,14 +12,17 @@ import {
12
12
  type SettingsSection,
13
13
  setNestedValue,
14
14
  } from "@aliou/pi-utils-settings";
15
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
15
+ import type {
16
+ ExtensionAPI,
17
+ ExtensionContext,
18
+ } from "@mariozechner/pi-coding-agent";
16
19
  import { getSettingsListTheme } from "@mariozechner/pi-coding-agent";
17
20
  import type { ApertureConfig, ResolvedConfig } from "../config";
18
21
  import { configLoader } from "../config";
19
22
 
20
23
  export function registerApertureSettings(
21
24
  pi: ExtensionAPI,
22
- onConfigChange: () => void,
25
+ onConfigChange: (ctx: ExtensionContext) => void,
23
26
  ): void {
24
27
  registerSettingsCommand<ApertureConfig, ResolvedConfig>(pi, {
25
28
  commandName: "aperture:settings",
@@ -89,8 +92,8 @@ export function registerApertureSettings(
89
92
  }
90
93
  return updated;
91
94
  },
92
- onSave: () => {
93
- onConfigChange();
95
+ onSave: (ctx) => {
96
+ onConfigChange(ctx);
94
97
  },
95
98
  });
96
99
  }
@@ -8,12 +8,15 @@
8
8
  */
9
9
 
10
10
  import { FuzzySelector } from "@aliou/pi-utils-settings";
11
- import { getProviders } from "@mariozechner/pi-ai";
12
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
11
+ import type {
12
+ ExtensionAPI,
13
+ ExtensionContext,
14
+ } from "@mariozechner/pi-coding-agent";
13
15
  import { getSettingsListTheme } from "@mariozechner/pi-coding-agent";
14
- import type { Component } from "@mariozechner/pi-tui";
16
+ import type { Component, TUI } from "@mariozechner/pi-tui";
15
17
  import { Input, Key, matchesKey } from "@mariozechner/pi-tui";
16
18
  import { configLoader } from "../config";
19
+ import { checkApertureHealth } from "../lib/health";
17
20
 
18
21
  function normalizeUrl(url: string): string {
19
22
  let result = url.trim();
@@ -24,6 +27,102 @@ function normalizeUrl(url: string): string {
24
27
  return result.replace(/\/v1\/?$/, "").replace(/\/+$/, "");
25
28
  }
26
29
 
30
+ const SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
31
+
32
+ /**
33
+ * Shows a spinner while verifying the Aperture URL is reachable.
34
+ * Kicks off the health check on construction and resolves done(boolean)
35
+ * when the check completes.
36
+ */
37
+ class HealthCheckSpinner implements Component {
38
+ private theme: ReturnType<typeof getSettingsListTheme>;
39
+ private url: string;
40
+ private tui: TUI;
41
+ // true = healthy, false = failed (retry), undefined = cancelled
42
+ private done: (result: boolean | undefined) => void;
43
+ private frame = 0;
44
+ private timer: ReturnType<typeof setInterval>;
45
+ private result: { ok: boolean; error?: string } | null = null;
46
+
47
+ constructor(
48
+ theme: ReturnType<typeof getSettingsListTheme>,
49
+ url: string,
50
+ tui: TUI,
51
+ done: (result: boolean | undefined) => void,
52
+ ) {
53
+ this.theme = theme;
54
+ this.url = url;
55
+ this.tui = tui;
56
+ this.done = done;
57
+
58
+ // Animate spinner at ~80ms per frame.
59
+ this.timer = setInterval(() => {
60
+ this.frame = (this.frame + 1) % SPINNER_FRAMES.length;
61
+ this.tui.requestRender();
62
+ }, 80);
63
+
64
+ // Fire the check.
65
+ checkApertureHealth(url).then((res) => {
66
+ clearInterval(this.timer);
67
+ this.result = res;
68
+ this.tui.requestRender();
69
+
70
+ // On success, auto-advance after a brief pause.
71
+ // On failure, wait for user input (see handleInput).
72
+ if (res.ok) {
73
+ setTimeout(() => this.done(true), 600);
74
+ }
75
+ });
76
+ }
77
+
78
+ render(_width: number): string[] {
79
+ const lines: string[] = [];
80
+ lines.push(this.theme.label(" Aperture Setup", true));
81
+ lines.push("");
82
+
83
+ if (!this.result) {
84
+ const spinner = SPINNER_FRAMES[this.frame];
85
+ lines.push(
86
+ this.theme.hint(` ${spinner} Checking connection to ${this.url}...`),
87
+ );
88
+ } else if (this.result.ok) {
89
+ lines.push(this.theme.hint(` Connected to ${this.url}`));
90
+ } else {
91
+ lines.push(
92
+ this.theme.hint(` Could not reach ${this.url}: ${this.result.error}`),
93
+ );
94
+ lines.push("");
95
+ lines.push(
96
+ this.theme.hint(
97
+ " Make sure the URL is correct and you are connected to the tailnet.",
98
+ ),
99
+ );
100
+ lines.push("");
101
+ lines.push(this.theme.hint(" Enter: try another URL · Esc: cancel"));
102
+ }
103
+
104
+ lines.push("");
105
+ return lines;
106
+ }
107
+
108
+ invalidate() {}
109
+
110
+ handleInput(data: string) {
111
+ // Only handle input after a failed check.
112
+ if (!this.result || this.result.ok) return;
113
+
114
+ if (matchesKey(data, Key.enter)) {
115
+ this.done(false); // retry
116
+ } else if (matchesKey(data, Key.escape)) {
117
+ this.done(undefined); // cancel wizard
118
+ }
119
+ }
120
+
121
+ dispose() {
122
+ clearInterval(this.timer);
123
+ }
124
+ }
125
+
27
126
  /**
28
127
  * Simple input prompt component for the base URL step.
29
128
  */
@@ -152,7 +251,7 @@ class ProviderMultiSelect implements Component {
152
251
 
153
252
  export function registerSetupCommand(
154
253
  pi: ExtensionAPI,
155
- onConfigChange: () => void,
254
+ onConfigChange: (ctx: ExtensionContext) => void,
156
255
  ): void {
157
256
  pi.registerCommand("aperture:setup", {
158
257
  description: "Configure Tailscale Aperture integration",
@@ -168,17 +267,39 @@ export function registerSetupCommand(
168
267
  const config = configLoader.getConfig();
169
268
  const settingsTheme = getSettingsListTheme();
170
269
 
171
- // Step 1: base URL
172
- const baseUrl = await ctx.ui.custom<string | undefined>(
173
- (_tui, _theme, _kb, done) => {
174
- return new UrlPrompt(settingsTheme, config.baseUrl, done);
175
- },
176
- );
270
+ // Step 1: base URL + health check loop.
271
+ // On failure, loop back to the URL prompt so the user can retry.
272
+ let baseUrl: string | undefined;
273
+ while (true) {
274
+ baseUrl = await ctx.ui.custom<string | undefined>(
275
+ (_tui, _theme, _kb, done) => {
276
+ return new UrlPrompt(
277
+ settingsTheme,
278
+ baseUrl ?? config.baseUrl,
279
+ done,
280
+ );
281
+ },
282
+ );
283
+
284
+ if (!baseUrl) return;
285
+ const urlToCheck = baseUrl;
286
+
287
+ const result = await ctx.ui.custom<boolean | undefined>(
288
+ (tui, _theme, _kb, done) => {
289
+ return new HealthCheckSpinner(settingsTheme, urlToCheck, tui, done);
290
+ },
291
+ );
177
292
 
178
- if (!baseUrl) return;
293
+ if (result === true) break; // healthy, proceed
294
+ if (result === undefined) return; // cancelled
295
+ }
179
296
 
180
297
  // Step 2: select providers
181
- const knownProviders = getProviders();
298
+ // Use model registry so custom/extension providers are included.
299
+ const knownProviders = Array.from(
300
+ new Set(ctx.modelRegistry.getAll().map((model) => model.provider)),
301
+ ).sort((a, b) => a.localeCompare(b));
302
+
182
303
  const providers = await ctx.ui.custom<string[] | undefined>(
183
304
  (_tui, _theme, _kb, done) => {
184
305
  return new ProviderMultiSelect(
@@ -194,7 +315,7 @@ export function registerSetupCommand(
194
315
 
195
316
  // Step 3: save and register
196
317
  await configLoader.save("global", { baseUrl, providers });
197
- onConfigChange();
318
+ onConfigChange(ctx);
198
319
  ctx.ui.notify(
199
320
  `Aperture configured: ${providers.length} provider(s) via ${baseUrl}`,
200
321
  "info",
package/src/index.ts CHANGED
@@ -6,34 +6,72 @@
6
6
  * overrides each provider's baseUrl and sets a dummy apiKey.
7
7
  */
8
8
 
9
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
9
+ import type {
10
+ ExtensionAPI,
11
+ ExtensionContext,
12
+ } from "@mariozechner/pi-coding-agent";
10
13
  import { registerApertureSettings } from "./commands/settings";
11
14
  import { registerSetupCommand } from "./commands/setup";
12
15
  import { configLoader } from "./config";
13
16
 
14
- function registerProviders(pi: ExtensionAPI): void {
17
+ function getApertureBaseUrl(): string | null {
15
18
  const config = configLoader.getConfig();
16
- if (!config.baseUrl || config.providers.length === 0) return;
17
-
18
- const baseUrl = config.baseUrl.replace(/\/+$/, "");
19
-
20
- for (const provider of config.providers) {
21
- pi.registerProvider(provider, {
22
- baseUrl: `${baseUrl}/v1`,
23
- apiKey: "-",
24
- });
25
- }
19
+ if (!config.baseUrl || config.providers.length === 0) return null;
20
+ return `${config.baseUrl.replace(/\/+$/, "")}/v1`;
26
21
  }
27
22
 
28
23
  export default async function (pi: ExtensionAPI): Promise<void> {
29
24
  await configLoader.load();
30
25
 
31
- const onConfigChange = () => {
32
- // Config is already reloaded by configLoader.save(), just re-register.
33
- registerProviders(pi);
26
+ const config = configLoader.getConfig();
27
+ let lastRegisteredProviders = [...config.providers];
28
+
29
+ // At load time, pi.registerProvider() queue is flushed by the runner.
30
+ const baseUrl = getApertureBaseUrl();
31
+ if (baseUrl) {
32
+ for (const provider of config.providers) {
33
+ pi.registerProvider(provider, { baseUrl, apiKey: "-" });
34
+ }
35
+ }
36
+
37
+ const onSetupComplete = (ctx: ExtensionContext) => {
38
+ const cfg = configLoader.getConfig();
39
+ const removedProviders = lastRegisteredProviders.filter(
40
+ (p) => !cfg.providers.includes(p),
41
+ );
42
+
43
+ const url = getApertureBaseUrl();
44
+ if (url) {
45
+ for (const provider of cfg.providers) {
46
+ ctx.modelRegistry.registerProvider(provider, {
47
+ baseUrl: url,
48
+ apiKey: "-",
49
+ });
50
+ }
51
+ }
52
+ lastRegisteredProviders = [...cfg.providers];
53
+
54
+ // The active model is a snapshot. If it belongs to a provider we just
55
+ // reconfigured, re-resolve it so the new baseUrl takes effect.
56
+ if (ctx.model && cfg.providers.includes(ctx.model.provider)) {
57
+ const updated = ctx.modelRegistry.find(ctx.model.provider, ctx.model.id);
58
+ if (updated) {
59
+ ctx.ui.notify(
60
+ `[aperture] re-routing ${ctx.model.id} through ${updated.baseUrl}`,
61
+ "info",
62
+ );
63
+ pi.setModel(updated);
64
+ }
65
+ }
66
+
67
+ if (removedProviders.length > 0) {
68
+ ctx.ui.notify(
69
+ `Removed providers (${removedProviders.join(", ")}) will revert after /reload`,
70
+ "warning",
71
+ );
72
+ }
34
73
  };
35
74
 
36
- registerProviders(pi);
37
- registerSetupCommand(pi, onConfigChange);
38
- registerApertureSettings(pi, onConfigChange);
75
+ registerSetupCommand(pi, onSetupComplete);
76
+ registerApertureSettings(pi, onSetupComplete);
39
77
  }
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Health check for the Aperture gateway.
3
+ *
4
+ * Hits GET <baseUrl>/v1/models to verify the gateway is reachable.
5
+ * Uses native fetch (no extra dependencies).
6
+ */
7
+
8
+ export interface HealthCheckResult {
9
+ ok: boolean;
10
+ error?: string;
11
+ }
12
+
13
+ export async function checkApertureHealth(
14
+ baseUrl: string,
15
+ ): Promise<HealthCheckResult> {
16
+ const url = `${baseUrl.replace(/\/+$/, "")}/v1/models`;
17
+ try {
18
+ const res = await fetch(url, {
19
+ method: "GET",
20
+ signal: AbortSignal.timeout(5000),
21
+ });
22
+ if (!res.ok) {
23
+ return { ok: false, error: `HTTP ${res.status} ${res.statusText}` };
24
+ }
25
+ return { ok: true };
26
+ } catch (e: unknown) {
27
+ const msg = e instanceof Error ? e.message : String(e);
28
+ return { ok: false, error: msg };
29
+ }
30
+ }
package/.envrc DELETED
@@ -1,21 +0,0 @@
1
- # shellcheck disable=2148,2086
2
-
3
- # Use `source_up` if part of a monorepo.
4
-
5
- use nix
6
-
7
- # Load layouts. This basically just sets up PATH and other env vars.
8
- # Possible values are "node", "bun", and "uv".
9
- #
10
- # Options:
11
- # --deny bin1,bin2,... Exclude binaries from local PATH, falling back to system.
12
- # Useful when a local binary shadows a system one you need.
13
- #
14
- # Examples:
15
- # layout node
16
- # layout node --deny pi,prettier
17
- # layout uv --deny python
18
-
19
- if has node; then
20
- layout node --deny pi
21
- fi
Binary file
Binary file
@@ -1 +0,0 @@
1
- $5f641c2a-07a9-45a1-b450-72c7d4d38754�(&path IN ('tsconfig.json','biome.json')