ts-procedures 10.2.0 → 10.2.2
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 +18 -0
- package/README.md +9 -6
- package/agent_config/bin/postinstall.mjs +20 -89
- package/agent_config/bin/setup.mjs +51 -264
- package/agent_config/lib/install-skills.mjs +108 -0
- package/build/codegen/bin/cli.js +1 -1
- package/build/codegen/bin/cli.js.map +1 -1
- package/build/codegen/emit/api-route.js +1 -1
- package/build/codegen/emit/api-route.js.map +1 -1
- package/build/codegen/emit/scope-file.js +1 -1
- package/build/codegen/emit/scope-file.js.map +1 -1
- package/build/codegen/emit-errors.integration.test.js +2 -2
- package/build/codegen/emit-errors.integration.test.js.map +1 -1
- package/build/codegen/resolve-envelope.js +1 -1
- package/build/codegen/resolve-envelope.js.map +1 -1
- package/build/codegen/test-helpers/run-tsc.js +1 -1
- package/build/codegen/test-helpers/run-tsc.js.map +1 -1
- package/build/exports.d.ts +1 -1
- package/build/exports.js.map +1 -1
- package/build/exports.surface.test.d.ts +1 -0
- package/build/exports.surface.test.js +132 -0
- package/build/exports.surface.test.js.map +1 -0
- package/docs/ai-agent-setup.md +19 -27
- package/docs/client-error-handling.md +1 -1
- package/docs/http-integrations.md +1 -1
- package/package.json +9 -8
- package/src/codegen/bin/cli.ts +1 -1
- package/src/codegen/emit/api-route.ts +1 -1
- package/src/codegen/emit/scope-file.ts +2 -1
- package/src/codegen/emit-errors.integration.test.ts +2 -2
- package/src/codegen/resolve-envelope.ts +1 -1
- package/src/codegen/test-helpers/run-tsc.ts +1 -0
- package/src/exports.surface.test.ts +134 -0
- package/src/exports.ts +1 -0
- package/agent_config/claude-code/.claude-plugin/plugin.json +0 -5
- package/agent_config/claude-code/agents/ts-procedures-architect.md +0 -138
- package/agent_config/copilot/copilot-instructions.md +0 -521
- package/agent_config/cursor/cursorrules +0 -521
- package/agent_config/lib/install-claude.mjs +0 -57
- /package/agent_config/{claude-code/skills → skills}/ts-procedures/SKILL.md +0 -0
- /package/agent_config/{claude-code/skills → skills}/ts-procedures/anti-patterns.md +0 -0
- /package/agent_config/{claude-code/skills → skills}/ts-procedures/api-reference.md +0 -0
- /package/agent_config/{claude-code/skills → skills}/ts-procedures/checklist.md +0 -0
- /package/agent_config/{claude-code/skills → skills}/ts-procedures/patterns.md +0 -0
- /package/agent_config/{claude-code/skills → skills}/ts-procedures/templates/astro-catchall.md +0 -0
- /package/agent_config/{claude-code/skills → skills}/ts-procedures/templates/client.md +0 -0
- /package/agent_config/{claude-code/skills → skills}/ts-procedures/templates/hono.md +0 -0
- /package/agent_config/{claude-code/skills → skills}/ts-procedures/templates/procedure.md +0 -0
- /package/agent_config/{claude-code/skills → skills}/ts-procedures/templates/stream-procedure.md +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,24 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
|
6
6
|
|
|
7
7
|
## Unreleased
|
|
8
8
|
|
|
9
|
+
## 10.2.2 — 2026-06-29
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- **Export `ProcedureResult` from the package root.** The shape every creator returns (`Create`/`CreateStream`/`CreateHttp`/`CreateHttpStream`) appears in the inferred return type of `Procedures(...)`, but the type itself was only reachable via an internal deep path. Downstream apps that assign a factory or creator result to an exported binding under declaration emit (e.g. `export const api = Procedures<Ctx>(...)`) hit `TS2742`/`TS2883` — _"The inferred type of '…' cannot be named without a reference to 'ProcedureResult' from '…/node_modules/ts-procedures/build/core/types.js'. This is likely not portable."_ Re-exporting `ProcedureResult` lets TypeScript name the type via the portable `ts-procedures` specifier. No runtime change.
|
|
14
|
+
|
|
15
|
+
## 10.2.1 — 2026-06-26
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
|
|
19
|
+
- **AI agent config is now a single spec-compliant [Agent Skill](https://www.anthropic.com/engineering/equipping-agents-for-the-real-world-with-agent-skills).** Replaced the per-tool config directories (`claude-code/`, `cursor/`, `copilot/`) with one source of truth at `agent_config/skills/ts-procedures/`. `npx ts-procedures-setup` now installs that one skill into the standard agent roots — `.agents/skills/ts-procedures/` (read by Codex, Cursor, GitHub Copilot, Gemini CLI, and 30+ tools) and `.claude/skills/ts-procedures/` (Claude Code native) — eliminating the hand-synced duplicate `.cursorrules` / `.github/copilot-instructions.md` files and their marker-merge machinery.
|
|
20
|
+
- `ts-procedures-setup` CLI simplified to `--dry-run` / `--check` / `--help` (per-tool `targets` and `--force` removed; the install is a force-overwrite copy). The postinstall auto-update remains opt-in (only refreshes once the skill is already installed).
|
|
21
|
+
|
|
22
|
+
### Removed
|
|
23
|
+
|
|
24
|
+
- The Claude-only `ts-procedures-architect` agent and the Claude Code plugin manifest (`.claude-plugin/plugin.json`). The unified `ts-procedures` skill covers reference, scaffold, and review modes.
|
|
25
|
+
- Downstream cleanup note: a previously installed `.claude/agents/ts-procedures-architect.md` is now orphaned and can be deleted manually.
|
|
26
|
+
|
|
9
27
|
## 10.2.0 — 2026-06-25
|
|
10
28
|
|
|
11
29
|
### Added
|
package/README.md
CHANGED
|
@@ -191,17 +191,20 @@ implementation.
|
|
|
191
191
|
|
|
192
192
|
## AI assistant setup
|
|
193
193
|
|
|
194
|
-
The package ships
|
|
195
|
-
|
|
194
|
+
The package ships a single spec-compliant [Agent Skill](https://www.anthropic.com/engineering/equipping-agents-for-the-real-world-with-agent-skills)
|
|
195
|
+
so your AI tooling knows the framework's patterns and footguns. One skill is
|
|
196
|
+
installed into the standard agent directories (`.agents/skills/` — read by
|
|
197
|
+
Codex, Cursor, Copilot, Gemini CLI and 30+ others — plus `.claude/skills/` for
|
|
198
|
+
Claude Code):
|
|
196
199
|
|
|
197
200
|
```bash
|
|
198
|
-
npx ts-procedures-setup #
|
|
199
|
-
npx ts-procedures-setup claude # Claude Code only
|
|
201
|
+
npx ts-procedures-setup # install the skill
|
|
200
202
|
npx ts-procedures-setup --dry-run # preview
|
|
203
|
+
npx ts-procedures-setup --check # CI gate: fail if outdated
|
|
201
204
|
```
|
|
202
205
|
|
|
203
|
-
|
|
204
|
-
postinstall hook.
|
|
206
|
+
The installed skill auto-updates on subsequent `npm install` via the package's
|
|
207
|
+
postinstall hook. See [docs/ai-agent-setup.md](docs/ai-agent-setup.md).
|
|
205
208
|
|
|
206
209
|
## Migrating from v8
|
|
207
210
|
|
|
@@ -1,105 +1,36 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
import { resolve, join, dirname } from 'node:path';
|
|
3
|
+
import { resolve, dirname } from 'node:path';
|
|
5
4
|
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import { installSkills, isInstalled } from '../lib/install-skills.mjs';
|
|
6
6
|
|
|
7
7
|
const __filename = fileURLToPath(import.meta.url);
|
|
8
8
|
const __dirname = dirname(__filename);
|
|
9
|
-
const AGENT_CONFIG_DIR = resolve(__dirname, '..');
|
|
10
9
|
|
|
11
|
-
|
|
12
|
-
const
|
|
10
|
+
// ─── Per-project config (keep in sync with bin/setup.mjs) ────────────────────
|
|
11
|
+
const CONFIG = {
|
|
12
|
+
packageName: 'ts-procedures',
|
|
13
|
+
sourceDir: resolve(__dirname, '..', 'skills'),
|
|
14
|
+
skills: ['ts-procedures'],
|
|
15
|
+
targetRoots: ['.agents/skills', '.claude/skills'],
|
|
16
|
+
};
|
|
13
17
|
|
|
14
|
-
// INIT_CWD is set by npm/yarn/pnpm during lifecycle scripts —
|
|
18
|
+
// INIT_CWD is set by npm/yarn/pnpm during lifecycle scripts — the project root.
|
|
15
19
|
const projectRoot = process.env.INIT_CWD;
|
|
20
|
+
if (!projectRoot) process.exit(0);
|
|
16
21
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
// Detect self-install (developing ts-procedures itself) — skip
|
|
22
|
-
const ownPackageRoot = resolve(__dirname, '../..');
|
|
23
|
-
if (resolve(projectRoot) === ownPackageRoot) {
|
|
24
|
-
process.exit(0);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
// ─── Helpers ──────────────────────────────────────────────
|
|
28
|
-
|
|
29
|
-
function readSourceFile(relativePath) {
|
|
30
|
-
return readFileSync(join(AGENT_CONFIG_DIR, relativePath), 'utf-8');
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function wrapWithMarkers(content) {
|
|
34
|
-
return `${MARKER_BEGIN}\n${content.trim()}\n${MARKER_END}`;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function replaceMarkerSection(existingContent, newContent) {
|
|
38
|
-
const beginIdx = existingContent.indexOf(MARKER_BEGIN);
|
|
39
|
-
const endIdx = existingContent.indexOf(MARKER_END);
|
|
40
|
-
|
|
41
|
-
if (beginIdx !== -1 && endIdx !== -1) {
|
|
42
|
-
const before = existingContent.slice(0, beginIdx);
|
|
43
|
-
const after = existingContent.slice(endIdx + MARKER_END.length);
|
|
44
|
-
return before + wrapWithMarkers(newContent) + after;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
return null; // No existing markers — don't modify
|
|
48
|
-
}
|
|
22
|
+
// Skip when developing this package itself.
|
|
23
|
+
if (resolve(projectRoot) === resolve(__dirname, '..', '..')) process.exit(0);
|
|
49
24
|
|
|
50
|
-
|
|
51
|
-
if (!existsSync(targetPath)) return false;
|
|
25
|
+
const cfg = { ...CONFIG, projectRoot };
|
|
52
26
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
const sourceContent = readSourceFile(sourceRelativePath);
|
|
57
|
-
const updated = replaceMarkerSection(existing, sourceContent);
|
|
58
|
-
if (updated) {
|
|
59
|
-
writeFileSync(targetPath, updated, 'utf-8');
|
|
60
|
-
return true;
|
|
61
|
-
}
|
|
62
|
-
return false;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
// ─── Auto-update ──────────────────────────────────────────
|
|
66
|
-
|
|
67
|
-
const claudeSkillDir = join(projectRoot, '.claude', 'skills', 'ts-procedures');
|
|
68
|
-
const cursorFile = join(projectRoot, '.cursorrules');
|
|
69
|
-
const copilotFile = join(projectRoot, '.github', 'copilot-instructions.md');
|
|
70
|
-
|
|
71
|
-
// Check if any target was previously set up
|
|
72
|
-
const hasAnySetup = existsSync(claudeSkillDir) || existsSync(cursorFile) || existsSync(copilotFile);
|
|
73
|
-
|
|
74
|
-
if (hasAnySetup) {
|
|
27
|
+
// Opt-in only: refresh skills the user has already installed; never create on
|
|
28
|
+
// first install (rely on README / `npx <pkg>-setup`).
|
|
29
|
+
if (isInstalled(cfg)) {
|
|
75
30
|
try {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
// Auto-update Claude Code files
|
|
79
|
-
if (existsSync(claudeSkillDir)) {
|
|
80
|
-
const { installClaude } = await import('../lib/install-claude.mjs');
|
|
81
|
-
installClaude(projectRoot);
|
|
82
|
-
updated.push('Claude Code');
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// Auto-update Cursor rules (only if markers present)
|
|
86
|
-
if (updateMarkedFile(cursorFile, 'cursor/cursorrules')) {
|
|
87
|
-
updated.push('Cursor');
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// Auto-update Copilot instructions (only if markers present)
|
|
91
|
-
if (updateMarkedFile(copilotFile, 'copilot/copilot-instructions.md')) {
|
|
92
|
-
updated.push('Copilot');
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
if (updated.length > 0) {
|
|
96
|
-
console.log('');
|
|
97
|
-
console.log(` ts-procedures — AI agent rules updated (${updated.join(', ')})`);
|
|
98
|
-
console.log('');
|
|
99
|
-
}
|
|
31
|
+
installSkills(cfg);
|
|
32
|
+
console.log(`\n ${CONFIG.packageName} — Agent Skill updated\n`);
|
|
100
33
|
} catch {
|
|
101
|
-
// Never break npm install
|
|
34
|
+
// Never break npm install.
|
|
102
35
|
}
|
|
103
36
|
}
|
|
104
|
-
|
|
105
|
-
// No invitation message if not opted in — rely on README / npx ts-procedures-setup --help
|
|
@@ -1,297 +1,84 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { existsSync } from 'node:fs';
|
|
4
|
+
import { dirname, resolve } from 'node:path';
|
|
5
5
|
import { fileURLToPath } from 'node:url';
|
|
6
|
-
import {
|
|
7
|
-
import { installClaude } from '../lib/install-claude.mjs';
|
|
6
|
+
import { installSkills, planSkills, outdatedSkillFiles } from '../lib/install-skills.mjs';
|
|
8
7
|
|
|
9
8
|
const __filename = fileURLToPath(import.meta.url);
|
|
10
9
|
const __dirname = dirname(__filename);
|
|
11
|
-
const AGENT_CONFIG_DIR = resolve(__dirname, '..');
|
|
12
|
-
const PROJECT_DIR = process.cwd();
|
|
13
|
-
|
|
14
|
-
const MARKER_BEGIN = '<!-- BEGIN ts-procedures -->';
|
|
15
|
-
const MARKER_END = '<!-- END ts-procedures -->';
|
|
16
10
|
|
|
17
|
-
|
|
11
|
+
// ─── Per-project config (the only thing a consuming library customizes) ──────
|
|
12
|
+
const CONFIG = {
|
|
13
|
+
packageName: 'ts-procedures',
|
|
14
|
+
sourceDir: resolve(__dirname, '..', 'skills'),
|
|
15
|
+
skills: ['ts-procedures'],
|
|
16
|
+
// `.agents/skills` is read by 30+ agent tools (Codex, Cursor, Copilot,
|
|
17
|
+
// Gemini CLI, …); `.claude/skills` is Claude Code's native project location.
|
|
18
|
+
// One source, fanned out to both.
|
|
19
|
+
targetRoots: ['.agents/skills', '.claude/skills'],
|
|
20
|
+
};
|
|
18
21
|
|
|
19
|
-
|
|
22
|
+
const PROJECT_DIR = process.cwd();
|
|
20
23
|
|
|
21
24
|
function printUsage() {
|
|
22
25
|
console.log(`
|
|
23
|
-
Usage: npx
|
|
26
|
+
Usage: npx ${CONFIG.packageName}-setup [options]
|
|
24
27
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
all Set up all targets (default)
|
|
28
|
+
Installs the ${CONFIG.packageName} Agent Skill into the standard agent
|
|
29
|
+
directories so any compatible AI coding tool picks it up automatically:
|
|
30
|
+
|
|
31
|
+
${CONFIG.targetRoots.map((r) => `${r}/${CONFIG.skills.join(', ')}`).join('\n ')}
|
|
30
32
|
|
|
31
33
|
Options:
|
|
32
34
|
--help Show this help message
|
|
33
|
-
--force Overwrite existing files without prompting
|
|
34
35
|
--dry-run Show what would be created/updated without writing files
|
|
35
|
-
--check Exit with code 1 if any files are outdated (
|
|
36
|
+
--check Exit with code 1 if any installed files are outdated (CI gate)
|
|
36
37
|
|
|
37
|
-
|
|
38
|
-
npx ts-procedures-setup # Set up all targets
|
|
39
|
-
npx ts-procedures-setup cursor # Set up Cursor only
|
|
40
|
-
npx ts-procedures-setup cursor copilot # Set up Cursor and Copilot
|
|
41
|
-
npx ts-procedures-setup --force # Overwrite without prompting
|
|
42
|
-
npx ts-procedures-setup --dry-run # Preview changes
|
|
43
|
-
npx ts-procedures-setup --check # Verify files are up to date
|
|
38
|
+
The files auto-update on every \`npm install\` once installed.
|
|
44
39
|
`);
|
|
45
40
|
}
|
|
46
41
|
|
|
47
|
-
function
|
|
48
|
-
const fullPath = join(AGENT_CONFIG_DIR, relativePath);
|
|
49
|
-
return readFileSync(fullPath, 'utf-8');
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
function wrapWithMarkers(content) {
|
|
53
|
-
return `${MARKER_BEGIN}\n${content.trim()}\n${MARKER_END}`;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
function replaceMarkerSection(existingContent, newContent) {
|
|
57
|
-
const beginIdx = existingContent.indexOf(MARKER_BEGIN);
|
|
58
|
-
const endIdx = existingContent.indexOf(MARKER_END);
|
|
59
|
-
|
|
60
|
-
if (beginIdx !== -1 && endIdx !== -1) {
|
|
61
|
-
const before = existingContent.slice(0, beginIdx);
|
|
62
|
-
const after = existingContent.slice(endIdx + MARKER_END.length);
|
|
63
|
-
return before + wrapWithMarkers(newContent) + after;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
const separator = existingContent.trim() ? '\n\n' : '';
|
|
67
|
-
return existingContent.trim() + separator + wrapWithMarkers(newContent) + '\n';
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
async function confirm(message) {
|
|
71
|
-
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
72
|
-
return new Promise((resolve) => {
|
|
73
|
-
rl.question(`${message} (y/N) `, (answer) => {
|
|
74
|
-
rl.close();
|
|
75
|
-
resolve(answer.toLowerCase() === 'y');
|
|
76
|
-
});
|
|
77
|
-
});
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// ─── Check Mode ───────────────────────────────────────────
|
|
81
|
-
|
|
82
|
-
function isFileOutdated(targetPath, expectedContent) {
|
|
83
|
-
if (!existsSync(targetPath)) return true;
|
|
84
|
-
const current = readFileSync(targetPath, 'utf-8');
|
|
85
|
-
return current !== expectedContent;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// ─── Target Handlers ──────────────────────────────────────
|
|
89
|
-
|
|
90
|
-
function setupClaude({ dryRun, check } = {}) {
|
|
91
|
-
if (dryRun) {
|
|
92
|
-
// Mirror install-claude.mjs: copy every file under each source skill dir, plus the agent.
|
|
93
|
-
const claudeFiles = [];
|
|
94
|
-
const skillsSrc = join(AGENT_CONFIG_DIR, 'claude-code', 'skills');
|
|
95
|
-
for (const skill of ['ts-procedures']) {
|
|
96
|
-
const walk = (dir, prefix) => {
|
|
97
|
-
for (const entry of readdirSync(dir)) {
|
|
98
|
-
const full = join(dir, entry);
|
|
99
|
-
if (statSync(full).isDirectory()) {
|
|
100
|
-
walk(full, `${prefix}/${entry}`);
|
|
101
|
-
} else {
|
|
102
|
-
claudeFiles.push(`${prefix}/${entry}`);
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
};
|
|
106
|
-
walk(join(skillsSrc, skill), `.claude/skills/${skill}`);
|
|
107
|
-
}
|
|
108
|
-
claudeFiles.push('.claude/agents/ts-procedures-architect.md');
|
|
109
|
-
|
|
110
|
-
console.log('\n Claude Code (dry run)');
|
|
111
|
-
console.log(' ─────────────────────');
|
|
112
|
-
for (const f of claudeFiles) {
|
|
113
|
-
const status = existsSync(join(PROJECT_DIR, f)) ? 'update' : 'create';
|
|
114
|
-
console.log(` [${status}] ${f}`);
|
|
115
|
-
}
|
|
116
|
-
console.log('');
|
|
117
|
-
return false;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
const { files } = installClaude(PROJECT_DIR);
|
|
121
|
-
|
|
122
|
-
if (check) return false; // installClaude always writes — check mode handled at main level
|
|
123
|
-
|
|
124
|
-
console.log('\n Claude Code');
|
|
125
|
-
console.log(' ───────────');
|
|
126
|
-
console.log(' Installed (updates automatically on npm install/update):\n');
|
|
127
|
-
for (const f of files) {
|
|
128
|
-
console.log(` ${f}`);
|
|
129
|
-
}
|
|
130
|
-
console.log('');
|
|
131
|
-
console.log(' Skills: ts-procedures (use `/ts-procedures review <path>` and `/ts-procedures scaffold <type> <Name>`; Kotlin/Swift codegen reference included)');
|
|
132
|
-
console.log(' Agent: ts-procedures-architect (architecture planning)\n');
|
|
133
|
-
return false;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
async function setupCursor(force, { dryRun, check } = {}) {
|
|
137
|
-
const targetPath = join(PROJECT_DIR, '.cursorrules');
|
|
138
|
-
const sourceContent = readSourceFile('cursor/cursorrules');
|
|
139
|
-
|
|
140
|
-
if (dryRun || check) {
|
|
141
|
-
const exists = existsSync(targetPath);
|
|
142
|
-
const hasMarkers = exists && readFileSync(targetPath, 'utf-8').includes(MARKER_BEGIN);
|
|
143
|
-
const label = dryRun ? 'dry run' : 'check';
|
|
144
|
-
const status = !exists ? 'create' : hasMarkers ? 'update' : 'append';
|
|
145
|
-
console.log(`\n Cursor (${label})`);
|
|
146
|
-
console.log(` ──────`);
|
|
147
|
-
console.log(` [${status}] .cursorrules\n`);
|
|
148
|
-
return check && (status === 'create' || status === 'update');
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
console.log('\n Cursor');
|
|
152
|
-
console.log(' ──────');
|
|
153
|
-
|
|
154
|
-
if (existsSync(targetPath)) {
|
|
155
|
-
const existing = readFileSync(targetPath, 'utf-8');
|
|
156
|
-
|
|
157
|
-
if (existing.includes(MARKER_BEGIN)) {
|
|
158
|
-
const updated = replaceMarkerSection(existing, sourceContent);
|
|
159
|
-
writeFileSync(targetPath, updated, 'utf-8');
|
|
160
|
-
console.log(' Updated ts-procedures section in .cursorrules\n');
|
|
161
|
-
return false;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
if (!force) {
|
|
165
|
-
const ok = await confirm(' .cursorrules already exists. Append ts-procedures rules?');
|
|
166
|
-
if (!ok) {
|
|
167
|
-
console.log(' Skipped.\n');
|
|
168
|
-
return false;
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
const updated = replaceMarkerSection(existing, sourceContent);
|
|
173
|
-
writeFileSync(targetPath, updated, 'utf-8');
|
|
174
|
-
console.log(' Appended ts-procedures rules to .cursorrules\n');
|
|
175
|
-
} else {
|
|
176
|
-
writeFileSync(targetPath, wrapWithMarkers(sourceContent) + '\n', 'utf-8');
|
|
177
|
-
console.log(' Created .cursorrules\n');
|
|
178
|
-
}
|
|
179
|
-
return false;
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
async function setupCopilot(force, { dryRun, check } = {}) {
|
|
183
|
-
const githubDir = join(PROJECT_DIR, '.github');
|
|
184
|
-
const targetPath = join(githubDir, 'copilot-instructions.md');
|
|
185
|
-
const sourceContent = readSourceFile('copilot/copilot-instructions.md');
|
|
186
|
-
|
|
187
|
-
if (dryRun || check) {
|
|
188
|
-
const exists = existsSync(targetPath);
|
|
189
|
-
const hasMarkers = exists && readFileSync(targetPath, 'utf-8').includes(MARKER_BEGIN);
|
|
190
|
-
const label = dryRun ? 'dry run' : 'check';
|
|
191
|
-
const status = !exists ? 'create' : hasMarkers ? 'update' : 'append';
|
|
192
|
-
console.log(`\n GitHub Copilot (${label})`);
|
|
193
|
-
console.log(` ──────────────`);
|
|
194
|
-
console.log(` [${status}] .github/copilot-instructions.md\n`);
|
|
195
|
-
return check && (status === 'create' || status === 'update');
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
console.log('\n GitHub Copilot');
|
|
199
|
-
console.log(' ──────────────');
|
|
200
|
-
|
|
201
|
-
if (!existsSync(githubDir)) {
|
|
202
|
-
mkdirSync(githubDir, { recursive: true });
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
if (existsSync(targetPath)) {
|
|
206
|
-
const existing = readFileSync(targetPath, 'utf-8');
|
|
207
|
-
|
|
208
|
-
if (existing.includes(MARKER_BEGIN)) {
|
|
209
|
-
const updated = replaceMarkerSection(existing, sourceContent);
|
|
210
|
-
writeFileSync(targetPath, updated, 'utf-8');
|
|
211
|
-
console.log(' Updated ts-procedures section in .github/copilot-instructions.md\n');
|
|
212
|
-
return false;
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
if (!force) {
|
|
216
|
-
const ok = await confirm(' .github/copilot-instructions.md already exists. Append ts-procedures instructions?');
|
|
217
|
-
if (!ok) {
|
|
218
|
-
console.log(' Skipped.\n');
|
|
219
|
-
return false;
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
const updated = replaceMarkerSection(existing, sourceContent);
|
|
224
|
-
writeFileSync(targetPath, updated, 'utf-8');
|
|
225
|
-
console.log(' Appended ts-procedures instructions to .github/copilot-instructions.md\n');
|
|
226
|
-
} else {
|
|
227
|
-
writeFileSync(targetPath, wrapWithMarkers(sourceContent) + '\n', 'utf-8');
|
|
228
|
-
console.log(' Created .github/copilot-instructions.md\n');
|
|
229
|
-
}
|
|
230
|
-
return false;
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
// ─── Main ─────────────────────────────────────────────────
|
|
234
|
-
|
|
235
|
-
async function main() {
|
|
42
|
+
function main() {
|
|
236
43
|
const args = process.argv.slice(2);
|
|
237
|
-
|
|
238
|
-
const dryRun = args.includes('--dry-run');
|
|
239
|
-
const check = args.includes('--check');
|
|
240
|
-
const help = args.includes('--help') || args.includes('-h');
|
|
241
|
-
const targets = args.filter(a => !a.startsWith('--') && !a.startsWith('-'));
|
|
242
|
-
|
|
243
|
-
if (help) {
|
|
44
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
244
45
|
printUsage();
|
|
245
46
|
process.exit(0);
|
|
246
47
|
}
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
console.error(`Unknown target: "${t}". Valid targets: ${VALID_TARGETS.join(', ')}`);
|
|
251
|
-
process.exit(1);
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
const selectedTargets = targets.length === 0 || targets.includes('all')
|
|
256
|
-
? ['claude', 'cursor', 'copilot']
|
|
257
|
-
: targets;
|
|
48
|
+
const dryRun = args.includes('--dry-run');
|
|
49
|
+
const check = args.includes('--check');
|
|
50
|
+
const cfg = { ...CONFIG, projectRoot: PROJECT_DIR };
|
|
258
51
|
|
|
259
52
|
const mode = dryRun ? ' (dry run)' : check ? ' (check)' : '';
|
|
260
|
-
console.log(`\
|
|
261
|
-
console.log('
|
|
262
|
-
|
|
263
|
-
const opts = { dryRun, check };
|
|
264
|
-
let outdated = false;
|
|
265
|
-
|
|
266
|
-
if (selectedTargets.includes('claude')) {
|
|
267
|
-
outdated = setupClaude(opts) || outdated;
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
if (selectedTargets.includes('cursor')) {
|
|
271
|
-
outdated = (await setupCursor(force, opts)) || outdated;
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
if (selectedTargets.includes('copilot')) {
|
|
275
|
-
outdated = (await setupCopilot(force, opts)) || outdated;
|
|
276
|
-
}
|
|
53
|
+
console.log(`\n${CONFIG.packageName} — Agent Skill setup${mode}`);
|
|
54
|
+
console.log('═'.repeat(40));
|
|
277
55
|
|
|
278
56
|
if (check) {
|
|
279
|
-
|
|
280
|
-
|
|
57
|
+
const outdated = outdatedSkillFiles(cfg);
|
|
58
|
+
if (outdated.length) {
|
|
59
|
+
console.log('\n Outdated or missing:');
|
|
60
|
+
for (const f of outdated) console.log(` ${f}`);
|
|
61
|
+
console.log(`\n Run: npx ${CONFIG.packageName}-setup\n`);
|
|
281
62
|
process.exit(1);
|
|
282
|
-
} else {
|
|
283
|
-
console.log(' All files are up to date.\n');
|
|
284
63
|
}
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
console.log('
|
|
64
|
+
console.log('\n All files are up to date.\n');
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (dryRun) {
|
|
69
|
+
console.log('');
|
|
70
|
+
for (const { rel, dest } of planSkills(cfg)) {
|
|
71
|
+
console.log(` [${existsSync(dest) ? 'update' : 'create'}] ${rel}`);
|
|
72
|
+
}
|
|
73
|
+
console.log('\n No files were modified (dry run).\n');
|
|
74
|
+
return;
|
|
291
75
|
}
|
|
76
|
+
|
|
77
|
+
const { files } = installSkills(cfg);
|
|
78
|
+
console.log('\n Installed (auto-updates on npm install):\n');
|
|
79
|
+
for (const f of files) console.log(` ${f}`);
|
|
80
|
+
console.log('\n Tip: these are generated — you may add the install roots to .gitignore.\n');
|
|
81
|
+
console.log('Done!\n');
|
|
292
82
|
}
|
|
293
83
|
|
|
294
|
-
main()
|
|
295
|
-
console.error('Error:', err.message);
|
|
296
|
-
process.exit(1);
|
|
297
|
-
});
|
|
84
|
+
main();
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { cpSync, mkdirSync, readdirSync, readFileSync, existsSync, statSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
|
|
4
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
5
|
+
// Generic, project-agnostic skill installer.
|
|
6
|
+
//
|
|
7
|
+
// This file is intentionally free of any ts-procedures specifics so it can be
|
|
8
|
+
// dropped verbatim into any library that ships Agent Skills. The only
|
|
9
|
+
// per-project surface is the config object the caller passes in (see
|
|
10
|
+
// bin/setup.mjs / bin/postinstall.mjs).
|
|
11
|
+
//
|
|
12
|
+
// A "skill" is a spec-compliant directory containing a SKILL.md (name +
|
|
13
|
+
// description frontmatter) plus optional references/templates. The same
|
|
14
|
+
// directory is read by 30+ agent tools from `.agents/skills/<name>/` and by
|
|
15
|
+
// Claude Code from `.claude/skills/<name>/`, so we fan the one source out to
|
|
16
|
+
// every configured target root.
|
|
17
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
18
|
+
|
|
19
|
+
/** Recursively list file paths under `dir`, relative to `dir`. */
|
|
20
|
+
export function listFilesRecursive(dir) {
|
|
21
|
+
const out = [];
|
|
22
|
+
for (const entry of readdirSync(dir)) {
|
|
23
|
+
const full = join(dir, entry);
|
|
24
|
+
if (statSync(full).isDirectory()) {
|
|
25
|
+
for (const nested of listFilesRecursive(full)) out.push(join(entry, nested));
|
|
26
|
+
} else {
|
|
27
|
+
out.push(entry);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return out;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Enumerate the install plan without touching the filesystem.
|
|
35
|
+
*
|
|
36
|
+
* @param {object} cfg
|
|
37
|
+
* @param {string} cfg.projectRoot — consuming project root (absolute)
|
|
38
|
+
* @param {string} cfg.sourceDir — dir containing the source skill folders (absolute)
|
|
39
|
+
* @param {string[]} cfg.skills — skill folder names under sourceDir
|
|
40
|
+
* @param {string[]} cfg.targetRoots — install roots relative to projectRoot
|
|
41
|
+
* (e.g. ['.agents/skills', '.claude/skills'])
|
|
42
|
+
* @returns {Array<{ skill: string, root: string, rel: string, src: string, dest: string }>}
|
|
43
|
+
*/
|
|
44
|
+
export function planSkills({ projectRoot, sourceDir, skills, targetRoots }) {
|
|
45
|
+
const plan = [];
|
|
46
|
+
for (const skill of skills) {
|
|
47
|
+
const skillSrc = join(sourceDir, skill);
|
|
48
|
+
for (const rel of listFilesRecursive(skillSrc)) {
|
|
49
|
+
for (const root of targetRoots) {
|
|
50
|
+
plan.push({
|
|
51
|
+
skill,
|
|
52
|
+
root,
|
|
53
|
+
rel: join(root, skill, rel),
|
|
54
|
+
src: join(skillSrc, rel),
|
|
55
|
+
dest: join(projectRoot, root, skill, rel),
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return plan;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Copy each source skill into every target root (force-overwrite).
|
|
65
|
+
*
|
|
66
|
+
* @param {object} cfg — same shape as planSkills
|
|
67
|
+
* @returns {{ files: string[] }} — installed paths relative to projectRoot
|
|
68
|
+
*/
|
|
69
|
+
export function installSkills({ projectRoot, sourceDir, skills, targetRoots }) {
|
|
70
|
+
const files = [];
|
|
71
|
+
for (const root of targetRoots) {
|
|
72
|
+
const rootDest = join(projectRoot, root);
|
|
73
|
+
mkdirSync(rootDest, { recursive: true });
|
|
74
|
+
for (const skill of skills) {
|
|
75
|
+
cpSync(join(sourceDir, skill), join(rootDest, skill), { recursive: true, force: true });
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
for (const { rel } of planSkills({ projectRoot, sourceDir, skills, targetRoots })) {
|
|
79
|
+
files.push(rel);
|
|
80
|
+
}
|
|
81
|
+
return { files };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Report which planned files are missing or differ from source (for --check).
|
|
86
|
+
*
|
|
87
|
+
* @param {object} cfg — same shape as planSkills
|
|
88
|
+
* @returns {string[]} — relative paths that are outdated (empty == up to date)
|
|
89
|
+
*/
|
|
90
|
+
export function outdatedSkillFiles(cfg) {
|
|
91
|
+
const outdated = [];
|
|
92
|
+
for (const { rel, src, dest } of planSkills(cfg)) {
|
|
93
|
+
if (!existsSync(dest) || readFileSync(dest, 'utf-8') !== readFileSync(src, 'utf-8')) {
|
|
94
|
+
outdated.push(rel);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return outdated;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** True if any skill was previously installed into any target root. */
|
|
101
|
+
export function isInstalled({ projectRoot, skills, targetRoots }) {
|
|
102
|
+
for (const root of targetRoots) {
|
|
103
|
+
for (const skill of skills) {
|
|
104
|
+
if (existsSync(join(projectRoot, root, skill))) return true;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return false;
|
|
108
|
+
}
|
package/build/codegen/bin/cli.js
CHANGED
|
@@ -21,7 +21,7 @@ export async function loadConfigFile(configPath) {
|
|
|
21
21
|
catch (err) {
|
|
22
22
|
if (configPath !== undefined) {
|
|
23
23
|
// Explicit path — always throw
|
|
24
|
-
throw new Error(`[ts-procedures-codegen] Failed to load config from ${filePath}: ${err instanceof Error ? err.message : err}
|
|
24
|
+
throw new Error(`[ts-procedures-codegen] Failed to load config from ${filePath}: ${err instanceof Error ? err.message : err}`, { cause: err });
|
|
25
25
|
}
|
|
26
26
|
// Default path — silently ignore if not found
|
|
27
27
|
return undefined;
|