@tanstack/ai-sandbox 0.5.2 → 0.5.3
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 +1 -0
- package/dist/esm/agents-file.d.ts +6 -0
- package/dist/esm/agents-file.js +21 -26
- package/dist/esm/agents-file.js.map +1 -1
- package/package.json +7 -6
- package/src/agents-file.ts +8 -35
package/README.md
CHANGED
|
@@ -52,6 +52,7 @@ Pick a **provider** package for where the sandbox runs:
|
|
|
52
52
|
| `@tanstack/ai-sandbox-cloudflare` | Cloudflare Workers + Containers |
|
|
53
53
|
| `@tanstack/ai-sandbox-vercel` | Vercel Sandbox |
|
|
54
54
|
| `@tanstack/ai-sandbox-daytona` | Daytona cloud sandboxes, snapshots |
|
|
55
|
+
| `@tanstack/ai-sandbox-upstash-box` | Upstash Box cloud sandboxes, snapshots |
|
|
55
56
|
| `@tanstack/ai-sandbox-sprites` | Sprites stateful sandboxes |
|
|
56
57
|
|
|
57
58
|
**Harness adapters** are separate packages. The default path is **Grok Build** (`@tanstack/ai-grok-build`); others include `@tanstack/ai-claude-code`, `@tanstack/ai-codex`, and `@tanstack/ai-opencode`. All require `withSandbox(...)` middleware — `chat()` fails fast without it.
|
|
@@ -31,6 +31,12 @@ export interface DiscoveredSkillDir {
|
|
|
31
31
|
* A flat clone with `SKILL.md` at the root is returned as one entry named
|
|
32
32
|
* after the clone. If no `SKILL.md` is found, the clone itself is returned
|
|
33
33
|
* so existing basename projection still works.
|
|
34
|
+
*
|
|
35
|
+
* The tree walk itself is the shared `walkSkillDirs` from `@tanstack/ai-skills`
|
|
36
|
+
* (parameterized over an injected lister — here `handle.fs.list`). The
|
|
37
|
+
* empty→clone-dir fallback is kept here because it is correct for harness
|
|
38
|
+
* projection but wrong for a skills catalog, so it must not live in the shared
|
|
39
|
+
* helper.
|
|
34
40
|
*/
|
|
35
41
|
export declare function discoverSkillDirs(handle: SandboxHandle, cloneDir: string): Promise<Array<DiscoveredSkillDir>>;
|
|
36
42
|
/** Format workspace scripts as a `## Workspace scripts` markdown section. */
|
package/dist/esm/agents-file.js
CHANGED
|
@@ -1,4 +1,18 @@
|
|
|
1
|
+
import { walkSkillDirs } from "@tanstack/ai-skills";
|
|
1
2
|
//#region src/agents-file.ts
|
|
3
|
+
/**
|
|
4
|
+
* Universal AGENTS.md writer with per-CLI symlink projection, plus the
|
|
5
|
+
* canonical helper for locating cloned gitSkill repositories inside a sandbox.
|
|
6
|
+
*
|
|
7
|
+
* The known-names set below lists the canonical instruction-file names for
|
|
8
|
+
* each AI coding assistant CLI. Keep the list in one place so it is easy to
|
|
9
|
+
* extend. The copy fallback ensures correctness on platforms without symlink
|
|
10
|
+
* support (e.g. Windows).
|
|
11
|
+
*
|
|
12
|
+
* External per-CLI convention: each assistant looks for its own instruction
|
|
13
|
+
* file by name (CLAUDE.md for Claude Code, GEMINI.md for Gemini CLI, …).
|
|
14
|
+
* We write a single authoritative AGENTS.md and point each name at it.
|
|
15
|
+
*/
|
|
2
16
|
/** CLI instruction-file names that should resolve to AGENTS.md. */
|
|
3
17
|
var SYMLINK_NAMES = ["CLAUDE.md", "GEMINI.md"];
|
|
4
18
|
/**
|
|
@@ -20,9 +34,6 @@ function resolveGitSkillDir(root, skill) {
|
|
|
20
34
|
const rawBasename = skill.repo.split("/").pop() ?? skill.repo;
|
|
21
35
|
return `${root}/.tanstack-skills/${rawBasename.endsWith(".git") ? rawBasename.slice(0, -4) : rawBasename}`;
|
|
22
36
|
}
|
|
23
|
-
var SKILL_FILE = "SKILL.md";
|
|
24
|
-
var SKIP_DIR_NAMES = /* @__PURE__ */ new Set([".git", "node_modules"]);
|
|
25
|
-
var MAX_SKILL_WALK_DEPTH = 6;
|
|
26
37
|
function basenameOf(path) {
|
|
27
38
|
const segments = path.split("/").filter((segment) => segment !== "");
|
|
28
39
|
return segments[segments.length - 1] ?? path;
|
|
@@ -35,37 +46,21 @@ function basenameOf(path) {
|
|
|
35
46
|
* A flat clone with `SKILL.md` at the root is returned as one entry named
|
|
36
47
|
* after the clone. If no `SKILL.md` is found, the clone itself is returned
|
|
37
48
|
* so existing basename projection still works.
|
|
49
|
+
*
|
|
50
|
+
* The tree walk itself is the shared `walkSkillDirs` from `@tanstack/ai-skills`
|
|
51
|
+
* (parameterized over an injected lister — here `handle.fs.list`). The
|
|
52
|
+
* empty→clone-dir fallback is kept here because it is correct for harness
|
|
53
|
+
* projection but wrong for a skills catalog, so it must not live in the shared
|
|
54
|
+
* helper.
|
|
38
55
|
*/
|
|
39
56
|
async function discoverSkillDirs(handle, cloneDir) {
|
|
40
|
-
const found =
|
|
41
|
-
await walkSkillDirs(handle, cloneDir, found, 0);
|
|
57
|
+
const found = await walkSkillDirs((dir) => handle.fs.list(dir), cloneDir);
|
|
42
58
|
if (found.length === 0) return [{
|
|
43
59
|
name: basenameOf(cloneDir),
|
|
44
60
|
dir: cloneDir
|
|
45
61
|
}];
|
|
46
62
|
return found;
|
|
47
63
|
}
|
|
48
|
-
async function walkSkillDirs(handle, dir, found, depth) {
|
|
49
|
-
if (depth > MAX_SKILL_WALK_DEPTH) return;
|
|
50
|
-
let entries;
|
|
51
|
-
try {
|
|
52
|
-
entries = await handle.fs.list(dir);
|
|
53
|
-
} catch {
|
|
54
|
-
return;
|
|
55
|
-
}
|
|
56
|
-
if (entries.some((entry) => entry.type === "file" && entry.name.toLowerCase() === SKILL_FILE.toLowerCase())) {
|
|
57
|
-
found.push({
|
|
58
|
-
name: basenameOf(dir),
|
|
59
|
-
dir
|
|
60
|
-
});
|
|
61
|
-
return;
|
|
62
|
-
}
|
|
63
|
-
for (const entry of entries) {
|
|
64
|
-
if (entry.type !== "dir") continue;
|
|
65
|
-
if (entry.name.startsWith(".") || SKIP_DIR_NAMES.has(entry.name)) continue;
|
|
66
|
-
await walkSkillDirs(handle, entry.path, found, depth + 1);
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
64
|
/** Format workspace scripts as a `## Workspace scripts` markdown section. */
|
|
70
65
|
function formatWorkspaceScriptsSection(scripts) {
|
|
71
66
|
const names = Object.keys(scripts).sort();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agents-file.js","names":[],"sources":["../../src/agents-file.ts"],"sourcesContent":["/**\n * Universal AGENTS.md writer with per-CLI symlink projection, plus the\n * canonical helper for locating cloned gitSkill repositories inside a sandbox.\n *\n * The known-names set below lists the canonical instruction-file names for\n * each AI coding assistant CLI. Keep the list in one place so it is easy to\n * extend. The copy fallback ensures correctness on platforms without symlink\n * support (e.g. Windows).\n *\n * External per-CLI convention: each assistant looks for its own instruction\n * file by name (CLAUDE.md for Claude Code, GEMINI.md for Gemini CLI, …).\n * We write a single authoritative AGENTS.md and point each name at it.\n */\nimport type { SandboxHandle } from './contracts'\nimport type { WorkspaceSkill } from './workspace'\n\n/** CLI instruction-file names that should resolve to AGENTS.md. */\nconst SYMLINK_NAMES: ReadonlyArray<string> = ['CLAUDE.md', 'GEMINI.md']\n\n/**\n * Resolve the directory a `gitSkill` repo is cloned into when no explicit\n * `into` override is provided. The convention is:\n *\n * `<root>/.tanstack-skills/<basename>`\n *\n * where `basename` is derived from the `repo` field by taking the last\n * path segment and stripping a trailing `.git` suffix.\n *\n * Per-harness projectors (e.g. the Claude Code adapter) import this helper\n * so they can locate cloned skill repos consistently.\n *\n * @param root - Workspace root inside the sandbox (e.g. `/workspace`).\n * @param skill - A `WorkspaceSkill` of `kind === 'git'`.\n */\nexport function resolveGitSkillDir(\n root: string,\n skill: Extract<WorkspaceSkill, { kind: 'git' }>,\n): string {\n const rawBasename = skill.repo.split('/').pop() ?? skill.repo\n const basename = rawBasename.endsWith('.git')\n ? rawBasename.slice(0, -4)\n : rawBasename\n return `${root}/.tanstack-skills/${basename}`\n}\n\n/** A folder that contains `SKILL.md`, ready to project under a harness skills dir. */\nexport interface DiscoveredSkillDir {\n name: string\n dir: string\n}\n\
|
|
1
|
+
{"version":3,"file":"agents-file.js","names":[],"sources":["../../src/agents-file.ts"],"sourcesContent":["/**\n * Universal AGENTS.md writer with per-CLI symlink projection, plus the\n * canonical helper for locating cloned gitSkill repositories inside a sandbox.\n *\n * The known-names set below lists the canonical instruction-file names for\n * each AI coding assistant CLI. Keep the list in one place so it is easy to\n * extend. The copy fallback ensures correctness on platforms without symlink\n * support (e.g. Windows).\n *\n * External per-CLI convention: each assistant looks for its own instruction\n * file by name (CLAUDE.md for Claude Code, GEMINI.md for Gemini CLI, …).\n * We write a single authoritative AGENTS.md and point each name at it.\n */\nimport { walkSkillDirs } from '@tanstack/ai-skills'\nimport type { SandboxHandle } from './contracts'\nimport type { WorkspaceSkill } from './workspace'\n\n/** CLI instruction-file names that should resolve to AGENTS.md. */\nconst SYMLINK_NAMES: ReadonlyArray<string> = ['CLAUDE.md', 'GEMINI.md']\n\n/**\n * Resolve the directory a `gitSkill` repo is cloned into when no explicit\n * `into` override is provided. The convention is:\n *\n * `<root>/.tanstack-skills/<basename>`\n *\n * where `basename` is derived from the `repo` field by taking the last\n * path segment and stripping a trailing `.git` suffix.\n *\n * Per-harness projectors (e.g. the Claude Code adapter) import this helper\n * so they can locate cloned skill repos consistently.\n *\n * @param root - Workspace root inside the sandbox (e.g. `/workspace`).\n * @param skill - A `WorkspaceSkill` of `kind === 'git'`.\n */\nexport function resolveGitSkillDir(\n root: string,\n skill: Extract<WorkspaceSkill, { kind: 'git' }>,\n): string {\n const rawBasename = skill.repo.split('/').pop() ?? skill.repo\n const basename = rawBasename.endsWith('.git')\n ? rawBasename.slice(0, -4)\n : rawBasename\n return `${root}/.tanstack-skills/${basename}`\n}\n\n/** A folder that contains `SKILL.md`, ready to project under a harness skills dir. */\nexport interface DiscoveredSkillDir {\n name: string\n dir: string\n}\n\nfunction basenameOf(path: string): string {\n const segments = path.split('/').filter((segment) => segment !== '')\n return segments[segments.length - 1] ?? path\n}\n\n/**\n * Find every skill folder under a cloned `gitSkill` repo.\n *\n * A skill folder is a directory that contains `SKILL.md`. Nested packs\n * (`skills/foo/SKILL.md`) are returned as `{ name: 'foo', dir: '…/skills/foo' }`.\n * A flat clone with `SKILL.md` at the root is returned as one entry named\n * after the clone. If no `SKILL.md` is found, the clone itself is returned\n * so existing basename projection still works.\n *\n * The tree walk itself is the shared `walkSkillDirs` from `@tanstack/ai-skills`\n * (parameterized over an injected lister — here `handle.fs.list`). The\n * empty→clone-dir fallback is kept here because it is correct for harness\n * projection but wrong for a skills catalog, so it must not live in the shared\n * helper.\n */\nexport async function discoverSkillDirs(\n handle: SandboxHandle,\n cloneDir: string,\n): Promise<Array<DiscoveredSkillDir>> {\n const found = await walkSkillDirs((dir) => handle.fs.list(dir), cloneDir)\n if (found.length === 0) {\n return [{ name: basenameOf(cloneDir), dir: cloneDir }]\n }\n return found\n}\n\n/** Format workspace scripts as a `## Workspace scripts` markdown section. */\nexport function formatWorkspaceScriptsSection(\n scripts: Record<string, string>,\n): string {\n const names = Object.keys(scripts).sort()\n if (names.length === 0) return ''\n const lines = names.map((name) => `- ${name} → ${scripts[name]}`)\n return `## Workspace scripts\\n\\n${lines.join('\\n')}`\n}\n\n/**\n * Merge base AGENTS.md content with an optional workspace scripts section.\n * Returns `undefined` when there is nothing to write.\n */\nexport function mergeAgentsContent(\n base: string | undefined,\n scripts: Record<string, string> | undefined,\n): string | undefined {\n const scriptsSection =\n scripts !== undefined ? formatWorkspaceScriptsSection(scripts) : ''\n if (base === undefined && scriptsSection.length === 0) return undefined\n if (base === undefined) return scriptsSection\n if (scriptsSection.length === 0) return base\n return `${base.trimEnd()}\\n\\n${scriptsSection}`\n}\n\n/** Escape a string for safe use as a single-quoted shell argument. */\nfunction sqEscape(value: string): string {\n return value.replace(/'/g, `'\\\\''`)\n}\n\n/**\n * Write `AGENTS.md` under `root` and create per-CLI symlinks (or copies as a\n * fallback when `ln -s` is unavailable).\n *\n * @param handle - The sandbox handle providing `fs` and `process`.\n * @param root - Absolute path inside the sandbox under which to write.\n * @param content - Markdown content for the instruction file.\n */\nexport async function writeAgentsFile(\n handle: SandboxHandle,\n root: string,\n content: string,\n): Promise<void> {\n const agentsPath = `${root}/AGENTS.md`\n await handle.fs.write(agentsPath, content)\n\n for (const name of SYMLINK_NAMES) {\n const lnCmd = `ln -s '${sqEscape('AGENTS.md')}' '${sqEscape(name)}'`\n const result = await handle.process.exec(lnCmd, { cwd: root })\n if (result.exitCode !== 0) {\n // Symlinks are not supported on this platform — fall back to a copy.\n await handle.fs.write(`${root}/${name}`, content)\n }\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAkBA,IAAM,gBAAuC,CAAC,aAAa,WAAW;;;;;;;;;;;;;;;;AAiBtE,SAAgB,mBACd,MACA,OACQ;CACR,MAAM,cAAc,MAAM,KAAK,MAAM,GAAG,CAAC,CAAC,IAAI,KAAK,MAAM;CAIzD,OAAO,GAAG,KAAK,oBAHE,YAAY,SAAS,MAAM,IACxC,YAAY,MAAM,GAAG,EAAE,IACvB;AAEN;AAQA,SAAS,WAAW,MAAsB;CACxC,MAAM,WAAW,KAAK,MAAM,GAAG,CAAC,CAAC,QAAQ,YAAY,YAAY,EAAE;CACnE,OAAO,SAAS,SAAS,SAAS,MAAM;AAC1C;;;;;;;;;;;;;;;;AAiBA,eAAsB,kBACpB,QACA,UACoC;CACpC,MAAM,QAAQ,MAAM,eAAe,QAAQ,OAAO,GAAG,KAAK,GAAG,GAAG,QAAQ;CACxE,IAAI,MAAM,WAAW,GACnB,OAAO,CAAC;EAAE,MAAM,WAAW,QAAQ;EAAG,KAAK;CAAS,CAAC;CAEvD,OAAO;AACT;;AAGA,SAAgB,8BACd,SACQ;CACR,MAAM,QAAQ,OAAO,KAAK,OAAO,CAAC,CAAC,KAAK;CACxC,IAAI,MAAM,WAAW,GAAG,OAAO;CAE/B,OAAO,2BADO,MAAM,KAAK,SAAS,KAAK,KAAK,KAAK,QAAQ,OACvB,CAAA,CAAM,KAAK,IAAI;AACnD;;;;;AAMA,SAAgB,mBACd,MACA,SACoB;CACpB,MAAM,iBACJ,YAAY,KAAA,IAAY,8BAA8B,OAAO,IAAI;CACnE,IAAI,SAAS,KAAA,KAAa,eAAe,WAAW,GAAG,OAAO,KAAA;CAC9D,IAAI,SAAS,KAAA,GAAW,OAAO;CAC/B,IAAI,eAAe,WAAW,GAAG,OAAO;CACxC,OAAO,GAAG,KAAK,QAAQ,EAAE,MAAM;AACjC;;AAGA,SAAS,SAAS,OAAuB;CACvC,OAAO,MAAM,QAAQ,MAAM,OAAO;AACpC;;;;;;;;;AAUA,eAAsB,gBACpB,QACA,MACA,SACe;CACf,MAAM,aAAa,GAAG,KAAK;CAC3B,MAAM,OAAO,GAAG,MAAM,YAAY,OAAO;CAEzC,KAAK,MAAM,QAAQ,eAAe;EAChC,MAAM,QAAQ,UAAU,SAAS,WAAW,EAAE,KAAK,SAAS,IAAI,EAAE;EAElE,KAAI,MADiB,OAAO,QAAQ,KAAK,OAAO,EAAE,KAAK,KAAK,CAAC,EAAA,CAClD,aAAa,GAEtB,MAAM,OAAO,GAAG,MAAM,GAAG,KAAK,GAAG,QAAQ,OAAO;CAEpD;AACF"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tanstack/ai-sandbox",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.3",
|
|
4
4
|
"description": "Provider-agnostic sandbox layer for TanStack AI — run harness adapters inside isolated sandboxes (defineSandbox, defineWorkspace, withSandbox) with a uniform SandboxHandle, workspace bootstrap, policy, and resumable lifecycle.",
|
|
5
5
|
"author": "",
|
|
6
6
|
"license": "MIT",
|
|
@@ -55,13 +55,14 @@
|
|
|
55
55
|
}
|
|
56
56
|
},
|
|
57
57
|
"dependencies": {
|
|
58
|
-
"@modelcontextprotocol/sdk": "^1.29.0"
|
|
58
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
59
|
+
"@tanstack/ai-skills": "^0.1.0"
|
|
59
60
|
},
|
|
60
61
|
"peerDependencies": {
|
|
61
62
|
"@ngrok/ngrok": "^1.0.0",
|
|
62
63
|
"vitest": "^4.1.10",
|
|
63
|
-
"@tanstack/ai": "^0.
|
|
64
|
-
"@tanstack/ai-persistence": "^0.5.
|
|
64
|
+
"@tanstack/ai": "^0.51.0",
|
|
65
|
+
"@tanstack/ai-persistence": "^0.5.3"
|
|
65
66
|
},
|
|
66
67
|
"peerDependenciesMeta": {
|
|
67
68
|
"@ngrok/ngrok": {
|
|
@@ -78,8 +79,8 @@
|
|
|
78
79
|
"@ngrok/ngrok": "^1.7.0",
|
|
79
80
|
"@vitest/coverage-v8": "4.1.10",
|
|
80
81
|
"vitest": "^4.1.10",
|
|
81
|
-
"@tanstack/ai": "0.
|
|
82
|
-
"@tanstack/ai-persistence": "0.5.
|
|
82
|
+
"@tanstack/ai": "0.51.0",
|
|
83
|
+
"@tanstack/ai-persistence": "0.5.3"
|
|
83
84
|
},
|
|
84
85
|
"scripts": {
|
|
85
86
|
"build": "vite build",
|
package/src/agents-file.ts
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
* file by name (CLAUDE.md for Claude Code, GEMINI.md for Gemini CLI, …).
|
|
12
12
|
* We write a single authoritative AGENTS.md and point each name at it.
|
|
13
13
|
*/
|
|
14
|
+
import { walkSkillDirs } from '@tanstack/ai-skills'
|
|
14
15
|
import type { SandboxHandle } from './contracts'
|
|
15
16
|
import type { WorkspaceSkill } from './workspace'
|
|
16
17
|
|
|
@@ -49,10 +50,6 @@ export interface DiscoveredSkillDir {
|
|
|
49
50
|
dir: string
|
|
50
51
|
}
|
|
51
52
|
|
|
52
|
-
const SKILL_FILE = 'SKILL.md'
|
|
53
|
-
const SKIP_DIR_NAMES = new Set(['.git', 'node_modules'])
|
|
54
|
-
const MAX_SKILL_WALK_DEPTH = 6
|
|
55
|
-
|
|
56
53
|
function basenameOf(path: string): string {
|
|
57
54
|
const segments = path.split('/').filter((segment) => segment !== '')
|
|
58
55
|
return segments[segments.length - 1] ?? path
|
|
@@ -66,48 +63,24 @@ function basenameOf(path: string): string {
|
|
|
66
63
|
* A flat clone with `SKILL.md` at the root is returned as one entry named
|
|
67
64
|
* after the clone. If no `SKILL.md` is found, the clone itself is returned
|
|
68
65
|
* so existing basename projection still works.
|
|
66
|
+
*
|
|
67
|
+
* The tree walk itself is the shared `walkSkillDirs` from `@tanstack/ai-skills`
|
|
68
|
+
* (parameterized over an injected lister — here `handle.fs.list`). The
|
|
69
|
+
* empty→clone-dir fallback is kept here because it is correct for harness
|
|
70
|
+
* projection but wrong for a skills catalog, so it must not live in the shared
|
|
71
|
+
* helper.
|
|
69
72
|
*/
|
|
70
73
|
export async function discoverSkillDirs(
|
|
71
74
|
handle: SandboxHandle,
|
|
72
75
|
cloneDir: string,
|
|
73
76
|
): Promise<Array<DiscoveredSkillDir>> {
|
|
74
|
-
const found
|
|
75
|
-
await walkSkillDirs(handle, cloneDir, found, 0)
|
|
77
|
+
const found = await walkSkillDirs((dir) => handle.fs.list(dir), cloneDir)
|
|
76
78
|
if (found.length === 0) {
|
|
77
79
|
return [{ name: basenameOf(cloneDir), dir: cloneDir }]
|
|
78
80
|
}
|
|
79
81
|
return found
|
|
80
82
|
}
|
|
81
83
|
|
|
82
|
-
async function walkSkillDirs(
|
|
83
|
-
handle: SandboxHandle,
|
|
84
|
-
dir: string,
|
|
85
|
-
found: Array<DiscoveredSkillDir>,
|
|
86
|
-
depth: number,
|
|
87
|
-
): Promise<void> {
|
|
88
|
-
if (depth > MAX_SKILL_WALK_DEPTH) return
|
|
89
|
-
let entries: Awaited<ReturnType<SandboxHandle['fs']['list']>>
|
|
90
|
-
try {
|
|
91
|
-
entries = await handle.fs.list(dir)
|
|
92
|
-
} catch {
|
|
93
|
-
return
|
|
94
|
-
}
|
|
95
|
-
const hasSkill = entries.some(
|
|
96
|
-
(entry) =>
|
|
97
|
-
entry.type === 'file' &&
|
|
98
|
-
entry.name.toLowerCase() === SKILL_FILE.toLowerCase(),
|
|
99
|
-
)
|
|
100
|
-
if (hasSkill) {
|
|
101
|
-
found.push({ name: basenameOf(dir), dir })
|
|
102
|
-
return
|
|
103
|
-
}
|
|
104
|
-
for (const entry of entries) {
|
|
105
|
-
if (entry.type !== 'dir') continue
|
|
106
|
-
if (entry.name.startsWith('.') || SKIP_DIR_NAMES.has(entry.name)) continue
|
|
107
|
-
await walkSkillDirs(handle, entry.path, found, depth + 1)
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
84
|
/** Format workspace scripts as a `## Workspace scripts` markdown section. */
|
|
112
85
|
export function formatWorkspaceScriptsSection(
|
|
113
86
|
scripts: Record<string, string>,
|