ts-procedures 5.2.0 → 5.4.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 (49) hide show
  1. package/README.md +150 -0
  2. package/agent_config/bin/postinstall.mjs +105 -0
  3. package/agent_config/bin/setup.mjs +286 -0
  4. package/agent_config/claude-code/.claude-plugin/plugin.json +5 -0
  5. package/agent_config/claude-code/agents/ts-procedures-architect.md +188 -0
  6. package/agent_config/claude-code/skills/guide/SKILL.md +142 -0
  7. package/agent_config/claude-code/skills/guide/anti-patterns.md +608 -0
  8. package/agent_config/claude-code/skills/guide/api-reference.md +696 -0
  9. package/agent_config/claude-code/skills/guide/patterns.md +727 -0
  10. package/agent_config/claude-code/skills/review/SKILL.md +53 -0
  11. package/agent_config/claude-code/skills/review/checklist.md +163 -0
  12. package/agent_config/claude-code/skills/scaffold/SKILL.md +56 -0
  13. package/agent_config/claude-code/skills/scaffold/templates/express-rpc.md +134 -0
  14. package/agent_config/claude-code/skills/scaffold/templates/hono-api.md +169 -0
  15. package/agent_config/claude-code/skills/scaffold/templates/hono-rpc.md +139 -0
  16. package/agent_config/claude-code/skills/scaffold/templates/hono-stream.md +134 -0
  17. package/agent_config/claude-code/skills/scaffold/templates/procedure.md +77 -0
  18. package/agent_config/claude-code/skills/scaffold/templates/stream-procedure.md +113 -0
  19. package/agent_config/copilot/copilot-instructions.md +290 -0
  20. package/agent_config/cursor/cursorrules +290 -0
  21. package/agent_config/lib/install-claude.mjs +109 -0
  22. package/build/implementations/http/hono-api/index.d.ts +102 -0
  23. package/build/implementations/http/hono-api/index.js +339 -0
  24. package/build/implementations/http/hono-api/index.js.map +1 -0
  25. package/build/implementations/http/hono-api/index.test.d.ts +1 -0
  26. package/build/implementations/http/hono-api/index.test.js +983 -0
  27. package/build/implementations/http/hono-api/index.test.js.map +1 -0
  28. package/build/implementations/http/hono-api/types.d.ts +13 -0
  29. package/build/implementations/http/hono-api/types.js +2 -0
  30. package/build/implementations/http/hono-api/types.js.map +1 -0
  31. package/build/implementations/types.d.ts +44 -0
  32. package/build/index.d.ts +28 -6
  33. package/build/index.js +28 -0
  34. package/build/index.js.map +1 -1
  35. package/build/schema/compute-schema.d.ts +5 -0
  36. package/build/schema/compute-schema.js +8 -1
  37. package/build/schema/compute-schema.js.map +1 -1
  38. package/build/schema/parser.d.ts +6 -5
  39. package/build/schema/parser.js +54 -0
  40. package/build/schema/parser.js.map +1 -1
  41. package/package.json +14 -4
  42. package/src/implementations/http/README.md +45 -2
  43. package/src/implementations/http/hono-api/index.test.ts +1328 -0
  44. package/src/implementations/http/hono-api/index.ts +461 -0
  45. package/src/implementations/http/hono-api/types.ts +16 -0
  46. package/src/implementations/types.ts +52 -0
  47. package/src/index.ts +87 -10
  48. package/src/schema/compute-schema.ts +23 -2
  49. package/src/schema/parser.ts +70 -3
package/README.md CHANGED
@@ -71,6 +71,41 @@ Create(name, config, handler)
71
71
  - `procedure` - Generic reference to the handler
72
72
  - `info` - Procedure meta (name, description, schema, `TExtendedConfig` properties, etc.)
73
73
 
74
+ ### Structured Input with schema.input
75
+
76
+ For HTTP APIs and other multi-channel transports, `schema.input` provides per-channel type safety. Each key is an independently validated input channel:
77
+
78
+ ```typescript
79
+ const { Create } = Procedures<AppContext, APIConfig>()
80
+
81
+ const { UpdateUser } = Create(
82
+ 'UpdateUser',
83
+ {
84
+ path: '/users/:id',
85
+ method: 'put',
86
+ schema: {
87
+ input: {
88
+ pathParams: Type.Object({ id: Type.String() }),
89
+ query: Type.Object({ notify: Type.Optional(Type.Boolean()) }),
90
+ body: Type.Object({ name: Type.String(), email: Type.String() }),
91
+ },
92
+ returnType: Type.Object({ ok: Type.Boolean() }),
93
+ },
94
+ },
95
+ async (ctx, { pathParams, query, body }) => {
96
+ // Each channel is independently typed and validated
97
+ await updateUser(pathParams.id, body)
98
+ if (query.notify) await sendNotification(pathParams.id)
99
+ return { ok: true }
100
+ }
101
+ )
102
+ ```
103
+
104
+ **Rules:**
105
+ - `schema.input` and `schema.params` are **mutually exclusive** — defining both throws `ProcedureRegistrationError`
106
+ - Each channel is validated independently with per-channel error messages
107
+ - Works with both `Create` and `CreateStream`
108
+
74
109
  ### CreateStream Function
75
110
 
76
111
  The `CreateStream` function defines streaming procedures that yield values over time using async generators:
@@ -625,6 +660,54 @@ app.listen(3000)
625
660
 
626
661
  See [Express RPC Integration Guide](src/implementations/http/express-rpc/README.md) for complete setup instructions including lifecycle hooks, error handling, and route documentation.
627
662
 
663
+ ### Hono API Integration
664
+
665
+ `ts-procedures` includes a REST-style HTTP integration for Hono that routes by HTTP method with per-channel input validation via `schema.input`.
666
+
667
+ ```typescript
668
+ import { Procedures } from 'ts-procedures'
669
+ import { HonoAPIAppBuilder } from 'ts-procedures/hono-api'
670
+ import type { APIConfig } from 'ts-procedures/http'
671
+ import { Type } from 'typebox'
672
+
673
+ const API = Procedures<{ userId: string }, APIConfig>()
674
+
675
+ API.Create('GetUser', {
676
+ path: '/users/:id',
677
+ method: 'get',
678
+ schema: {
679
+ input: {
680
+ pathParams: Type.Object({ id: Type.String() }),
681
+ },
682
+ returnType: Type.Object({ id: Type.String(), name: Type.String() }),
683
+ },
684
+ }, async (ctx, { pathParams }) => {
685
+ return await fetchUser(pathParams.id)
686
+ })
687
+
688
+ API.Create('CreateUser', {
689
+ path: '/users',
690
+ method: 'post',
691
+ schema: {
692
+ input: {
693
+ body: Type.Object({ name: Type.String(), email: Type.String() }),
694
+ },
695
+ },
696
+ }, async (ctx, { body }) => {
697
+ return await createUser(body)
698
+ })
699
+
700
+ const app = await new HonoAPIAppBuilder({ pathPrefix: '/api' })
701
+ .register(API, (c) => ({ userId: c.req.header('x-user-id') || 'anonymous' }))
702
+ .build()
703
+
704
+ // Routes:
705
+ // GET /api/users/:id → 200
706
+ // POST /api/users → 201
707
+ ```
708
+
709
+ See [Hono API Integration Guide](src/implementations/http/hono-api/) for complete setup.
710
+
628
711
  ### Introspection with getProcedures()
629
712
 
630
713
  Access all registered procedures for documentation or routing:
@@ -769,8 +852,75 @@ import {
769
852
  TSchemaValidationError,
770
853
  Prettify,
771
854
  } from 'ts-procedures'
855
+
856
+ // HTTP types
857
+ import type { RPCConfig, RPCHttpRouteDoc, StreamHttpRouteDoc, StreamMode, APIConfig, APIHttpRouteDoc, APIInput, HttpMethod } from 'ts-procedures/http'
858
+
859
+ // Hono API (REST-style)
860
+ import { HonoAPIAppBuilder } from 'ts-procedures/hono-api'
861
+ import type { APIConfig, APIHttpRouteDoc, APIInput, HttpMethod, QueryParser } from 'ts-procedures/hono-api'
862
+ ```
863
+
864
+ ## AI Agent Setup
865
+
866
+ ts-procedures ships with built-in AI assistant configuration for **Claude Code**, **Cursor**, and **GitHub Copilot**. This gives AI tools framework-aware context when writing ts-procedures code in your project.
867
+
868
+ ### Quick Setup
869
+
870
+ ```bash
871
+ npx ts-procedures-setup
872
+ ```
873
+
874
+ This installs rules for all supported AI tools. You can also target specific tools:
875
+
876
+ ```bash
877
+ npx ts-procedures-setup claude # Claude Code only
878
+ npx ts-procedures-setup cursor # Cursor only
879
+ npx ts-procedures-setup copilot # GitHub Copilot only
880
+ ```
881
+
882
+ ### What Gets Installed
883
+
884
+ | Tool | Files | Auto-updates? |
885
+ |------|-------|---------------|
886
+ | **Claude Code** | `.claude/rules/ts-procedures.md`, `.claude/commands/ts-procedures-scaffold.md`, `.claude/commands/ts-procedures-review.md`, `.claude/agents/ts-procedures-architect.md` | Yes |
887
+ | **Cursor** | `.cursorrules` (marker-based section) | Yes |
888
+ | **GitHub Copilot** | `.github/copilot-instructions.md` (marker-based section) | Yes |
889
+
890
+ ### Auto-Updates
891
+
892
+ After initial setup, rules are automatically refreshed on every `npm install` or `npm update`. When ts-procedures publishes a new version, your AI tools get the latest framework guidance without any manual steps.
893
+
894
+ ### Claude Code Features
895
+
896
+ Once installed, Claude Code gets:
897
+
898
+ - **Framework reference** — auto-loaded rules with core API, schema system, error handling, and decision framework
899
+ - **Scaffold command** — `/project:ts-procedures-scaffold <type> <Name>` generates procedures, streams, and HTTP setups with correct patterns
900
+ - **Review command** — `/project:ts-procedures-review <path>` checks code against a 60+ item checklist
901
+ - **Architecture agent** — `ts-procedures-architect` helps plan procedure structure, schema design, and HTTP implementation choices
902
+
903
+ ### CLI Options
904
+
905
+ ```bash
906
+ npx ts-procedures-setup --force # Overwrite without prompting
907
+ npx ts-procedures-setup --dry-run # Preview what would be created/updated
908
+ npx ts-procedures-setup --check # Exit with code 1 if files are outdated (for CI)
772
909
  ```
773
910
 
911
+ ### Gitignore
912
+
913
+ The `.claude/` files are auto-generated and regenerated on `npm install`. You can add them to `.gitignore`:
914
+
915
+ ```gitignore
916
+ # Auto-generated AI agent rules (regenerated on npm install)
917
+ .claude/rules/ts-procedures.md
918
+ .claude/commands/ts-procedures-*.md
919
+ .claude/agents/ts-procedures-*.md
920
+ ```
921
+
922
+ Cursor and Copilot files use marker-based sections that coexist with your own rules, so they should typically be committed.
923
+
774
924
  ## License
775
925
 
776
926
  MIT
@@ -0,0 +1,105 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { readFileSync, writeFileSync, existsSync } from 'node:fs';
4
+ import { resolve, join, dirname } from 'node:path';
5
+ import { fileURLToPath } from 'node:url';
6
+
7
+ const __filename = fileURLToPath(import.meta.url);
8
+ const __dirname = dirname(__filename);
9
+ const AGENT_CONFIG_DIR = resolve(__dirname, '..');
10
+
11
+ const MARKER_BEGIN = '<!-- BEGIN ts-procedures -->';
12
+ const MARKER_END = '<!-- END ts-procedures -->';
13
+
14
+ // INIT_CWD is set by npm/yarn/pnpm during lifecycle scripts — points to the project root
15
+ const projectRoot = process.env.INIT_CWD;
16
+
17
+ if (!projectRoot) {
18
+ process.exit(0);
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
+ }
49
+
50
+ function updateMarkedFile(targetPath, sourceRelativePath) {
51
+ if (!existsSync(targetPath)) return false;
52
+
53
+ const existing = readFileSync(targetPath, 'utf-8');
54
+ if (!existing.includes(MARKER_BEGIN)) return false;
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 rulesFile = join(projectRoot, '.claude', 'rules', 'ts-procedures.md');
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(rulesFile) || existsSync(cursorFile) || existsSync(copilotFile);
73
+
74
+ if (hasAnySetup) {
75
+ try {
76
+ const updated = [];
77
+
78
+ // Auto-update Claude Code files
79
+ if (existsSync(rulesFile)) {
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
+ }
100
+ } catch {
101
+ // Never break npm install
102
+ }
103
+ }
104
+
105
+ // No invitation message if not opted in — rely on README / npx ts-procedures-setup --help
@@ -0,0 +1,286 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'node:fs';
4
+ import { join, dirname, resolve } from 'node:path';
5
+ import { fileURLToPath } from 'node:url';
6
+ import { createInterface } from 'node:readline';
7
+ import { installClaude } from '../lib/install-claude.mjs';
8
+
9
+ const __filename = fileURLToPath(import.meta.url);
10
+ 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
+
17
+ const VALID_TARGETS = ['claude', 'cursor', 'copilot', 'all'];
18
+
19
+ // ─── Helpers ──────────────────────────────────────────────
20
+
21
+ function printUsage() {
22
+ console.log(`
23
+ Usage: npx ts-procedures-setup [targets...] [options]
24
+
25
+ Targets:
26
+ claude Install Claude Code rules and commands into .claude/
27
+ cursor Copy ts-procedures rules to .cursorrules
28
+ copilot Copy ts-procedures instructions to .github/copilot-instructions.md
29
+ all Set up all targets (default)
30
+
31
+ Options:
32
+ --help Show this help message
33
+ --force Overwrite existing files without prompting
34
+ --dry-run Show what would be created/updated without writing files
35
+ --check Exit with code 1 if any files are outdated (useful in CI)
36
+
37
+ Examples:
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
44
+ `);
45
+ }
46
+
47
+ function readSourceFile(relativePath) {
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
+ const claudeFiles = [
93
+ '.claude/rules/ts-procedures.md',
94
+ '.claude/commands/ts-procedures-scaffold.md',
95
+ '.claude/commands/ts-procedures-review.md',
96
+ '.claude/agents/ts-procedures-architect.md',
97
+ ];
98
+ console.log('\n Claude Code (dry run)');
99
+ console.log(' ─────────────────────');
100
+ for (const f of claudeFiles) {
101
+ const status = existsSync(join(PROJECT_DIR, f)) ? 'update' : 'create';
102
+ console.log(` [${status}] ${f}`);
103
+ }
104
+ console.log('');
105
+ return false;
106
+ }
107
+
108
+ const { files } = installClaude(PROJECT_DIR);
109
+
110
+ if (check) return false; // installClaude always writes — check mode handled at main level
111
+
112
+ console.log('\n Claude Code');
113
+ console.log(' ───────────');
114
+ console.log(' Installed (updates automatically on npm install/update):\n');
115
+ for (const f of files) {
116
+ console.log(` ${f}`);
117
+ }
118
+ console.log('');
119
+ console.log(' Commands: /project:ts-procedures-scaffold, /project:ts-procedures-review');
120
+ console.log(' Agent: ts-procedures-architect (architecture planning)');
121
+ console.log(' Reference: Auto-loaded via .claude/rules/ts-procedures.md\n');
122
+ return false;
123
+ }
124
+
125
+ async function setupCursor(force, { dryRun, check } = {}) {
126
+ const targetPath = join(PROJECT_DIR, '.cursorrules');
127
+ const sourceContent = readSourceFile('cursor/cursorrules');
128
+
129
+ if (dryRun || check) {
130
+ const exists = existsSync(targetPath);
131
+ const hasMarkers = exists && readFileSync(targetPath, 'utf-8').includes(MARKER_BEGIN);
132
+ const label = dryRun ? 'dry run' : 'check';
133
+ const status = !exists ? 'create' : hasMarkers ? 'update' : 'append';
134
+ console.log(`\n Cursor (${label})`);
135
+ console.log(` ──────`);
136
+ console.log(` [${status}] .cursorrules\n`);
137
+ return check && (status === 'create' || status === 'update');
138
+ }
139
+
140
+ console.log('\n Cursor');
141
+ console.log(' ──────');
142
+
143
+ if (existsSync(targetPath)) {
144
+ const existing = readFileSync(targetPath, 'utf-8');
145
+
146
+ if (existing.includes(MARKER_BEGIN)) {
147
+ const updated = replaceMarkerSection(existing, sourceContent);
148
+ writeFileSync(targetPath, updated, 'utf-8');
149
+ console.log(' Updated ts-procedures section in .cursorrules\n');
150
+ return false;
151
+ }
152
+
153
+ if (!force) {
154
+ const ok = await confirm(' .cursorrules already exists. Append ts-procedures rules?');
155
+ if (!ok) {
156
+ console.log(' Skipped.\n');
157
+ return false;
158
+ }
159
+ }
160
+
161
+ const updated = replaceMarkerSection(existing, sourceContent);
162
+ writeFileSync(targetPath, updated, 'utf-8');
163
+ console.log(' Appended ts-procedures rules to .cursorrules\n');
164
+ } else {
165
+ writeFileSync(targetPath, wrapWithMarkers(sourceContent) + '\n', 'utf-8');
166
+ console.log(' Created .cursorrules\n');
167
+ }
168
+ return false;
169
+ }
170
+
171
+ async function setupCopilot(force, { dryRun, check } = {}) {
172
+ const githubDir = join(PROJECT_DIR, '.github');
173
+ const targetPath = join(githubDir, 'copilot-instructions.md');
174
+ const sourceContent = readSourceFile('copilot/copilot-instructions.md');
175
+
176
+ if (dryRun || check) {
177
+ const exists = existsSync(targetPath);
178
+ const hasMarkers = exists && readFileSync(targetPath, 'utf-8').includes(MARKER_BEGIN);
179
+ const label = dryRun ? 'dry run' : 'check';
180
+ const status = !exists ? 'create' : hasMarkers ? 'update' : 'append';
181
+ console.log(`\n GitHub Copilot (${label})`);
182
+ console.log(` ──────────────`);
183
+ console.log(` [${status}] .github/copilot-instructions.md\n`);
184
+ return check && (status === 'create' || status === 'update');
185
+ }
186
+
187
+ console.log('\n GitHub Copilot');
188
+ console.log(' ──────────────');
189
+
190
+ if (!existsSync(githubDir)) {
191
+ mkdirSync(githubDir, { recursive: true });
192
+ }
193
+
194
+ if (existsSync(targetPath)) {
195
+ const existing = readFileSync(targetPath, 'utf-8');
196
+
197
+ if (existing.includes(MARKER_BEGIN)) {
198
+ const updated = replaceMarkerSection(existing, sourceContent);
199
+ writeFileSync(targetPath, updated, 'utf-8');
200
+ console.log(' Updated ts-procedures section in .github/copilot-instructions.md\n');
201
+ return false;
202
+ }
203
+
204
+ if (!force) {
205
+ const ok = await confirm(' .github/copilot-instructions.md already exists. Append ts-procedures instructions?');
206
+ if (!ok) {
207
+ console.log(' Skipped.\n');
208
+ return false;
209
+ }
210
+ }
211
+
212
+ const updated = replaceMarkerSection(existing, sourceContent);
213
+ writeFileSync(targetPath, updated, 'utf-8');
214
+ console.log(' Appended ts-procedures instructions to .github/copilot-instructions.md\n');
215
+ } else {
216
+ writeFileSync(targetPath, wrapWithMarkers(sourceContent) + '\n', 'utf-8');
217
+ console.log(' Created .github/copilot-instructions.md\n');
218
+ }
219
+ return false;
220
+ }
221
+
222
+ // ─── Main ─────────────────────────────────────────────────
223
+
224
+ async function main() {
225
+ const args = process.argv.slice(2);
226
+ const force = args.includes('--force');
227
+ const dryRun = args.includes('--dry-run');
228
+ const check = args.includes('--check');
229
+ const help = args.includes('--help') || args.includes('-h');
230
+ const targets = args.filter(a => !a.startsWith('--') && !a.startsWith('-'));
231
+
232
+ if (help) {
233
+ printUsage();
234
+ process.exit(0);
235
+ }
236
+
237
+ for (const t of targets) {
238
+ if (!VALID_TARGETS.includes(t)) {
239
+ console.error(`Unknown target: "${t}". Valid targets: ${VALID_TARGETS.join(', ')}`);
240
+ process.exit(1);
241
+ }
242
+ }
243
+
244
+ const selectedTargets = targets.length === 0 || targets.includes('all')
245
+ ? ['claude', 'cursor', 'copilot']
246
+ : targets;
247
+
248
+ const mode = dryRun ? ' (dry run)' : check ? ' (check)' : '';
249
+ console.log(`\nts-procedures AI Agent Setup${mode}`);
250
+ console.log('════════════════════════════');
251
+
252
+ const opts = { dryRun, check };
253
+ let outdated = false;
254
+
255
+ if (selectedTargets.includes('claude')) {
256
+ outdated = setupClaude(opts) || outdated;
257
+ }
258
+
259
+ if (selectedTargets.includes('cursor')) {
260
+ outdated = (await setupCursor(force, opts)) || outdated;
261
+ }
262
+
263
+ if (selectedTargets.includes('copilot')) {
264
+ outdated = (await setupCopilot(force, opts)) || outdated;
265
+ }
266
+
267
+ if (check) {
268
+ if (outdated) {
269
+ console.log(' Files are outdated. Run: npx ts-procedures-setup\n');
270
+ process.exit(1);
271
+ } else {
272
+ console.log(' All files are up to date.\n');
273
+ }
274
+ } else if (dryRun) {
275
+ console.log(' No files were modified (dry run).\n');
276
+ } else {
277
+ console.log(' Tip: Auto-generated .claude/ files can be added to .gitignore');
278
+ console.log(' (they are regenerated on npm install).\n');
279
+ console.log('Done!\n');
280
+ }
281
+ }
282
+
283
+ main().catch((err) => {
284
+ console.error('Error:', err.message);
285
+ process.exit(1);
286
+ });
@@ -0,0 +1,5 @@
1
+ {
2
+ "name": "ts-procedures",
3
+ "version": "1.0.0",
4
+ "description": "AI coding assistant plugin for ts-procedures — a TypeScript RPC framework for type-safe, schema-validated procedure calls with automatic validation, streaming support, and HTTP framework integrations."
5
+ }