tina4-nodejs 3.10.34 → 3.10.38

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tina4-nodejs",
3
- "version": "3.10.34",
3
+ "version": "3.10.38",
4
4
  "type": "module",
5
5
  "description": "This is not a framework. Tina4 for Node.js/TypeScript — zero deps, 38 built-in features.",
6
6
  "keywords": ["tina4", "framework", "web", "api", "orm", "graphql", "websocket", "typescript"],
@@ -24,7 +24,7 @@ const HELP = `
24
24
  tina4nodejs routes List all registered routes
25
25
  tina4nodejs test [file] Run project tests
26
26
  tina4nodejs generate <what> <name> Generate scaffolding (model, route, migration, middleware)
27
- tina4nodejs ai Detect AI coding tools and install context
27
+ tina4nodejs ai Install AI coding assistant context files
28
28
  tina4nodejs help Show this help message
29
29
 
30
30
  Options:
@@ -79,34 +79,15 @@ async function main(): Promise<void> {
79
79
  break;
80
80
  }
81
81
  case "ai": {
82
- const { detectAi, installAiContext, installAllAiContext, aiStatusReport } = await import("../../core/src/ai.js");
82
+ const { showMenu, installSelected, installAll } = await import("../../core/src/ai.js");
83
83
  const root = args[1] || ".";
84
- const installAll = args.includes("--all");
85
- const force = args.includes("--force");
86
84
 
87
- // Show status
88
- console.log(aiStatusReport(root));
89
-
90
- // Install context
91
- if (installAll) {
92
- const created = installAllAiContext(root, force);
93
- if (created.length > 0) {
94
- console.log("Installed AI context files:");
95
- for (const f of created) {
96
- console.log(` + ${f}`);
97
- }
98
- } else {
99
- console.log("All AI context files already exist (use --force to overwrite).");
100
- }
85
+ if (args.includes("--all")) {
86
+ installAll(root);
101
87
  } else {
102
- const created = installAiContext(root, { force });
103
- if (created.length > 0) {
104
- console.log("Installed AI context files:");
105
- for (const f of created) {
106
- console.log(` + ${f}`);
107
- }
108
- } else {
109
- console.log("No new AI context files needed.");
88
+ const selection = await showMenu(root);
89
+ if (selection) {
90
+ installSelected(root, selection);
110
91
  }
111
92
  }
112
93
  break;
@@ -36,8 +36,9 @@ export async function serveProject(options: ServeOptions): Promise<void> {
36
36
  const watcher = watchForChanges(watchDirs, async () => {
37
37
  try {
38
38
  const { discoverRoutes } = await import("@tina4/core");
39
- const routes = await discoverRoutes(routesDir);
39
+ // Clear routes BEFORE re-discovery to avoid stale duplicates
40
40
  server.router.clear();
41
+ const routes = await discoverRoutes(routesDir);
41
42
  for (const route of routes) {
42
43
  server.router.addRoute(route);
43
44
  }
@@ -1,137 +1,229 @@
1
1
  /**
2
- * Tina4 AI — Detect AI coding assistants and scaffold context files.
2
+ * Tina4 AI — Install AI coding assistant context files.
3
3
  *
4
- * Detect which AI coding tools are available and install framework-aware
5
- * context so that any AI assistant understands how to build with Tina4.
4
+ * Simple menu-driven installer for AI tool context files.
5
+ * The user picks which tools they use, we install the appropriate files.
6
6
  *
7
- * import { detectAi, installAiContext } from "@tina4/core";
8
- *
9
- * const tools = detectAi(); // [{ name: "claude-code", ... }]
10
- * installAiContext(); // Scaffold context for all detected tools
7
+ * import { showMenu, installSelected } from "@tina4/core";
11
8
  */
12
- import { existsSync, mkdirSync, writeFileSync, readdirSync, readFileSync, copyFileSync, cpSync, statSync } from "node:fs";
9
+ import { existsSync, mkdirSync, writeFileSync, readdirSync, readFileSync, cpSync, statSync } from "node:fs";
13
10
  import { join, resolve, relative, dirname } from "node:path";
14
11
  import { fileURLToPath } from "node:url";
12
+ import { execSync } from "node:child_process";
13
+ import { createInterface } from "node:readline";
15
14
 
16
15
  // ── Types ────────────────────────────────────────────────────
17
16
 
18
17
  export interface AiTool {
19
18
  name: string;
20
19
  description: string;
21
- configDir: string | null;
22
20
  contextFile: string;
21
+ configDir: string | null;
23
22
  }
24
23
 
25
- export interface AiDetection {
26
- name: string;
27
- description: string;
28
- configFile: string;
29
- status: "detected" | "not-detected";
24
+ // ── Tool definitions (ordered array) ────────────────────────
25
+
26
+ export const AI_TOOLS: AiTool[] = [
27
+ { name: "claude-code", description: "Claude Code", contextFile: "CLAUDE.md", configDir: ".claude" },
28
+ { name: "cursor", description: "Cursor", contextFile: ".cursorules", configDir: ".cursor" },
29
+ { name: "copilot", description: "GitHub Copilot", contextFile: ".github/copilot-instructions.md", configDir: ".github" },
30
+ { name: "windsurf", description: "Windsurf", contextFile: ".windsurfrules", configDir: null },
31
+ { name: "aider", description: "Aider", contextFile: "CONVENTIONS.md", configDir: null },
32
+ { name: "cline", description: "Cline", contextFile: ".clinerules", configDir: null },
33
+ { name: "codex", description: "OpenAI Codex", contextFile: "AGENTS.md", configDir: null },
34
+ ];
35
+
36
+ // ── Helpers ─────────────────────────────────────────────────
37
+
38
+ const GREEN = "\x1b[32m";
39
+ const YELLOW = "\x1b[33m";
40
+ const RESET = "\x1b[0m";
41
+
42
+ /**
43
+ * Check if a tool's context file already exists.
44
+ */
45
+ export function isInstalled(root: string, tool: AiTool): boolean {
46
+ return existsSync(join(resolve(root), tool.contextFile));
30
47
  }
31
48
 
32
- // ── Tool definitions ─────────────────────────────────────────
33
-
34
- const AI_TOOLS: Record<string, AiTool> = {
35
- "claude-code": {
36
- name: "claude-code",
37
- description: "Claude Code (Anthropic CLI)",
38
- configDir: ".claude",
39
- contextFile: "CLAUDE.md",
40
- },
41
- cursor: {
42
- name: "cursor",
43
- description: "Cursor IDE",
44
- configDir: ".cursor",
45
- contextFile: ".cursorules",
46
- },
47
- copilot: {
48
- name: "copilot",
49
- description: "GitHub Copilot",
50
- configDir: ".github",
51
- contextFile: ".github/copilot-instructions.md",
52
- },
53
- windsurf: {
54
- name: "windsurf",
55
- description: "Windsurf (Codeium)",
56
- configDir: null,
57
- contextFile: ".windsurfrules",
58
- },
59
- aider: {
60
- name: "aider",
61
- description: "Aider",
62
- configDir: null,
63
- contextFile: "CONVENTIONS.md",
64
- },
65
- cline: {
66
- name: "cline",
67
- description: "Cline (VS Code)",
68
- configDir: null,
69
- contextFile: ".clinerules",
70
- },
71
- codex: {
72
- name: "codex",
73
- description: "OpenAI Codex CLI",
74
- configDir: null,
75
- contextFile: "AGENTS.md",
76
- },
77
- };
78
-
79
- // ── Detection helpers ────────────────────────────────────────
80
-
81
- function detectTool(root: string, toolName: string): boolean {
49
+ /**
50
+ * Print the numbered menu and read user input via readline.
51
+ * Returns a promise that resolves to the user's selection string.
52
+ */
53
+ export function showMenu(root: string = "."): Promise<string> {
82
54
  const r = resolve(root);
83
- switch (toolName) {
84
- case "claude-code":
85
- return existsSync(join(r, ".claude")) || existsSync(join(r, "CLAUDE.md"));
86
- case "cursor":
87
- return existsSync(join(r, ".cursor")) || existsSync(join(r, ".cursorules"));
88
- case "copilot":
89
- return existsSync(join(r, ".github", "copilot-instructions.md")) || existsSync(join(r, ".github"));
90
- case "windsurf":
91
- return existsSync(join(r, ".windsurfrules"));
92
- case "aider":
93
- return existsSync(join(r, ".aider.conf.yml")) || existsSync(join(r, "CONVENTIONS.md"));
94
- case "cline":
95
- return existsSync(join(r, ".clinerules"));
96
- case "codex":
97
- return existsSync(join(r, "AGENTS.md")) || existsSync(join(r, "codex.md"));
98
- default:
99
- return false;
55
+
56
+ console.log("\n Tina4 AI Context Installer\n");
57
+
58
+ for (let i = 0; i < AI_TOOLS.length; i++) {
59
+ const tool = AI_TOOLS[i];
60
+ const installed = isInstalled(r, tool);
61
+ const marker = installed ? ` ${GREEN}[installed]${RESET}` : "";
62
+ const desc = tool.description.padEnd(20);
63
+ console.log(` ${i + 1}. ${desc} ${tool.contextFile}${marker}`);
64
+ }
65
+
66
+ // tina4-ai tools option
67
+ let tina4AiInstalled = false;
68
+ try {
69
+ execSync("which mdview", { stdio: "ignore" });
70
+ tina4AiInstalled = true;
71
+ } catch {
72
+ // not installed
73
+ }
74
+ const tina4AiMarker = tina4AiInstalled ? ` ${GREEN}[installed]${RESET}` : "";
75
+ console.log(` 8. Install tina4-ai tools (requires Python)${tina4AiMarker}`);
76
+ console.log();
77
+
78
+ return new Promise<string>((resolve) => {
79
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
80
+ rl.question(" Select (comma-separated, or 'all'): ", (answer) => {
81
+ rl.close();
82
+ resolve(answer.trim());
83
+ });
84
+ });
85
+ }
86
+
87
+ /**
88
+ * Install context files for the selected tools.
89
+ *
90
+ * selection: comma-separated numbers like "1,2,3" or "all"
91
+ * Returns list of created/updated file paths.
92
+ */
93
+ export function installSelected(root: string, selection: string): string[] {
94
+ const rootPath = resolve(root);
95
+ const created: string[] = [];
96
+
97
+ let indices: number[];
98
+ let doInstallTina4Ai = false;
99
+
100
+ if (selection.toLowerCase() === "all") {
101
+ indices = AI_TOOLS.map((_, i) => i);
102
+ doInstallTina4Ai = true;
103
+ } else {
104
+ const parts = selection.split(",").map((s) => s.trim()).filter(Boolean);
105
+ indices = [];
106
+ for (const p of parts) {
107
+ const n = parseInt(p, 10);
108
+ if (isNaN(n)) continue;
109
+ if (n === 8) {
110
+ doInstallTina4Ai = true;
111
+ } else if (n >= 1 && n <= AI_TOOLS.length) {
112
+ indices.push(n - 1);
113
+ }
114
+ }
115
+ }
116
+
117
+ const context = generateContext();
118
+
119
+ for (const idx of indices) {
120
+ const tool = AI_TOOLS[idx];
121
+ const files = installForTool(rootPath, tool, context);
122
+ created.push(...files);
123
+ }
124
+
125
+ if (doInstallTina4Ai) {
126
+ installTina4Ai();
100
127
  }
128
+
129
+ return created;
101
130
  }
102
131
 
103
- // ── Public API ───────────────────────────────────────────────
132
+ /**
133
+ * Install context for all AI tools (non-interactive).
134
+ */
135
+ export function installAll(root: string = "."): string[] {
136
+ return installSelected(root, "all");
137
+ }
104
138
 
105
139
  /**
106
- * Detect which AI coding tools are present in the project.
140
+ * Install context file for a single tool.
107
141
  */
108
- export function detectAi(root: string = "."): AiDetection[] {
109
- const r = resolve(root);
110
- const results: AiDetection[] = [];
111
-
112
- for (const [name, tool] of Object.entries(AI_TOOLS)) {
113
- results.push({
114
- name,
115
- description: tool.description,
116
- configFile: tool.contextFile,
117
- status: detectTool(r, name) ? "detected" : "not-detected",
118
- });
142
+ function installForTool(root: string, tool: AiTool, context: string): string[] {
143
+ const created: string[] = [];
144
+ const contextPath = join(root, tool.contextFile);
145
+
146
+ // Create directories
147
+ if (tool.configDir) {
148
+ mkdirSync(join(root, tool.configDir), { recursive: true });
149
+ }
150
+ const parentDir = dirname(contextPath);
151
+ mkdirSync(parentDir, { recursive: true });
152
+
153
+ // Always overwrite -- user chose to install
154
+ writeFileSync(contextPath, context, "utf-8");
155
+ const rel = relative(root, contextPath);
156
+ created.push(rel);
157
+ console.log(` ${GREEN}\u2713${RESET} Updated ${rel}`);
158
+
159
+ // Claude-specific extras
160
+ if (tool.name === "claude-code") {
161
+ const skills = installClaudeSkills(root);
162
+ created.push(...skills);
119
163
  }
120
164
 
121
- return results;
165
+ return created;
166
+ }
167
+
168
+ /**
169
+ * Install tina4-ai package (provides mdview for markdown viewing).
170
+ */
171
+ function installTina4Ai(): void {
172
+ console.log(" Installing tina4-ai tools...");
173
+ for (const cmd of ["pip3", "pip"]) {
174
+ let hasCmd = false;
175
+ try {
176
+ execSync(`which ${cmd}`, { stdio: "ignore" });
177
+ hasCmd = true;
178
+ } catch {
179
+ // not available
180
+ }
181
+ if (!hasCmd) continue;
182
+
183
+ try {
184
+ execSync(`${cmd} install --upgrade tina4-ai`, { stdio: "pipe", timeout: 60000 });
185
+ console.log(` ${GREEN}\u2713${RESET} Installed tina4-ai (mdview)`);
186
+ return;
187
+ } catch (err: any) {
188
+ const stderr = err.stderr ? err.stderr.toString().trim().slice(0, 100) : "unknown error";
189
+ console.log(` ${YELLOW}!${RESET} ${cmd} failed: ${stderr}`);
190
+ }
191
+ }
192
+ console.log(` ${YELLOW}!${RESET} Python/pip not available -- skip tina4-ai`);
122
193
  }
123
194
 
124
195
  /**
125
- * Return just the names of detected AI tools.
196
+ * Copy Claude Code skill files from the framework's directories.
126
197
  */
127
- export function detectAiNames(root: string = "."): string[] {
128
- return detectAi(root)
129
- .filter((t) => t.status === "detected")
130
- .map((t) => t.name);
198
+ function installClaudeSkills(root: string): string[] {
199
+ const created: string[] = [];
200
+
201
+ // Determine the framework root (where packages/core/src/ lives)
202
+ const thisDir = dirname(fileURLToPath(import.meta.url));
203
+ const frameworkRoot = resolve(thisDir, "..", "..", "..");
204
+
205
+ // Copy skill directories from .claude/skills/ in the framework to the project
206
+ const frameworkSkillsDir = join(frameworkRoot, ".claude", "skills");
207
+ if (existsSync(frameworkSkillsDir)) {
208
+ const targetSkillsDir = join(root, ".claude", "skills");
209
+ mkdirSync(targetSkillsDir, { recursive: true });
210
+ for (const entry of readdirSync(frameworkSkillsDir)) {
211
+ const skillDir = join(frameworkSkillsDir, entry);
212
+ if (existsSync(skillDir) && statSync(skillDir).isDirectory()) {
213
+ const targetDir = join(targetSkillsDir, entry);
214
+ cpSync(skillDir, targetDir, { recursive: true, force: true });
215
+ const rel = relative(root, targetDir);
216
+ created.push(rel);
217
+ console.log(` ${GREEN}\u2713${RESET} Updated ${rel}`);
218
+ }
219
+ }
220
+ }
221
+
222
+ return created;
131
223
  }
132
224
 
133
225
  /**
134
- * Generate a universal Tina4 context document for any AI assistant.
226
+ * Generate the universal Tina4 context document for any AI assistant.
135
227
  */
136
228
  export function generateContext(): string {
137
229
  return `# Tina4 Node.js — AI Context
@@ -144,23 +236,24 @@ web framework with zero third-party dependencies for core features.
144
236
  ## Quick Start
145
237
 
146
238
  \`\`\`bash
147
- tina4nodejs init . # Scaffold project
148
- tina4nodejs serve # Start dev server on port 7148
149
- tina4nodejs migrate # Run database migrations
150
- tina4nodejs test # Run test suite
151
- tina4nodejs routes # List all registered routes
239
+ npx tina4nodejs init . # Scaffold project
240
+ npx tina4nodejs serve # Start dev server on port 7148
241
+ npx tina4nodejs migrate # Run database migrations
242
+ npx tina4nodejs test # Run test suite
243
+ npx tina4nodejs routes # List all registered routes
152
244
  \`\`\`
153
245
 
154
246
  ## Project Structure
155
247
 
156
248
  \`\`\`
157
- src/routes/ Route handlers (auto-discovered, file-based routing)
158
- src/models/ ORM models (one per file, convention-based)
159
- src/templates/ Twig templates
160
- src/public/ Static assets served at /
161
- src/scss/ SCSS files (auto-compiled to public/css/)
162
- migrations/ SQL migration files (sequential numbered)
163
- test/ Test files
249
+ packages/core/src/ Framework core (server, router, middleware, events)
250
+ src/routes/ Route handlers (auto-discovered, file-based routing)
251
+ src/models/ ORM models (one per file, convention-based)
252
+ src/templates/ Twig templates
253
+ src/public/ Static assets served at /
254
+ src/scss/ SCSS files (auto-compiled to public/css/)
255
+ migrations/ SQL migration files (sequential numbered)
256
+ test/ — Test files
164
257
  \`\`\`
165
258
 
166
259
  ## Built-in Features (No External Packages Needed)
@@ -170,8 +263,8 @@ test/ — Test files
170
263
  | Routing | router | \`import { get, post, put, del } from "@tina4/core"\` |
171
264
  | ORM | orm | \`import { BaseModel } from "@tina4/orm"\` |
172
265
  | Database | database | \`import { initDatabase } from "@tina4/orm"\` |
173
- | Templates | twig | \`import { renderTemplate } from "@tina4/frond"\` |
174
- | JWT Auth | auth | \`import { createToken, validateToken } from "@tina4/core"\` |
266
+ | Templates | twig | \`import { renderTemplate } from "@tina4/twig"\` |
267
+ | JWT Auth | auth | \`import { createToken, validateToken, hashPassword, checkPassword } from "@tina4/core"\` |
175
268
  | REST API Client | api | \`import { Api } from "@tina4/core"\` |
176
269
  | GraphQL | graphql | \`import { GraphQL } from "@tina4/core"\` |
177
270
  | WebSocket | websocket | \`import { WebSocketServer } from "@tina4/core"\` |
@@ -179,12 +272,18 @@ test/ — Test files
179
272
  | Email (SMTP+IMAP) | messenger | \`import { Messenger } from "@tina4/core"\` |
180
273
  | Background Queue | queue | \`import { Queue } from "@tina4/core"\` |
181
274
  | SCSS Compilation | scss | Auto-compiled from src/scss/ |
182
- | Migrations | migration | \`tina4nodejs migrate\` CLI command |
275
+ | Migrations | migration | \`npx tina4nodejs migrate\` CLI command |
276
+ | Seeder | seeder | \`import { FakeData, seedTable } from "@tina4/orm"\` |
183
277
  | i18n | i18n | \`import { I18n } from "@tina4/core"\` |
184
278
  | Swagger/OpenAPI | swagger | Auto-generated at /swagger |
185
279
  | Sessions | session | \`import { Session } from "@tina4/core"\` |
186
280
  | Middleware | middleware | \`import { MiddlewareChain } from "@tina4/core"\` |
187
- | Cache | cache | \`import { responseCache } from "@tina4/core"\` |
281
+ | Cache | cache | \`import { responseCache, cacheStats, clearCache } from "@tina4/core"\` |
282
+ | Events | events | \`import { Events } from "@tina4/core"\` |
283
+ | HTML Builder | htmlElement | \`import { HtmlElement, htmlElement, addHtmlHelpers } from "@tina4/core"\` |
284
+ | Error Overlay | errorOverlay | \`import { renderErrorOverlay, isDebugMode } from "@tina4/core"\` |
285
+ | Inline Testing | testing | \`import { tests, assertEqual, runAllTests } from "@tina4/core"\` |
286
+ | DI Container | container | \`import { Container } from "@tina4/core"\` |
188
287
 
189
288
  ## Key Conventions
190
289
 
@@ -197,6 +296,30 @@ test/ — Test files
197
296
  7. **All schema changes via migrations** — never create tables in route code
198
297
  8. **Use built-in features** — never install packages for things Tina4 already provides
199
298
 
299
+ ## AI Workflow — Available Skills
300
+
301
+ When using an AI coding assistant with Tina4, these skills are available:
302
+
303
+ | Skill | Description |
304
+ |-------|-------------|
305
+ | \`/tina4-route\` | Create a new route with proper decorators and auth |
306
+ | \`/tina4-orm\` | Create an ORM model with migration |
307
+ | \`/tina4-crud\` | Generate complete CRUD (migration, ORM, routes, template, tests) |
308
+ | \`/tina4-auth\` | Set up JWT authentication with login/register |
309
+ | \`/tina4-api\` | Create an external API integration |
310
+ | \`/tina4-queue\` | Set up background job processing |
311
+ | \`/tina4-template\` | Create a server-rendered template page |
312
+ | \`/tina4-graphql\` | Set up a GraphQL endpoint |
313
+ | \`/tina4-websocket\` | Set up WebSocket communication |
314
+ | \`/tina4-wsdl\` | Create a SOAP/WSDL service |
315
+ | \`/tina4-messenger\` | Set up email send/receive |
316
+ | \`/tina4-test\` | Write tests for a feature |
317
+ | \`/tina4-migration\` | Create a database migration |
318
+ | \`/tina4-seed\` | Generate fake data for development |
319
+ | \`/tina4-i18n\` | Set up internationalization |
320
+ | \`/tina4-scss\` | Set up SCSS stylesheets |
321
+ | \`/tina4-frontend\` | Set up a frontend framework |
322
+
200
323
  ## Common Patterns
201
324
 
202
325
  ### Route
@@ -227,133 +350,4 @@ export default class User {
227
350
  `;
228
351
  }
229
352
 
230
- /**
231
- * Install Tina4 context files for detected (or specified) AI tools.
232
- *
233
- * Returns list of files created/updated.
234
- */
235
- export function installAiContext(
236
- root: string = ".",
237
- options?: { tools?: string[]; force?: boolean },
238
- ): string[] {
239
- const r = resolve(root);
240
- const force = options?.force ?? false;
241
- const created: string[] = [];
242
-
243
- const toolNames = options?.tools ?? detectAiNames(r);
244
- const context = generateContext();
245
-
246
- for (const toolName of toolNames) {
247
- const tool = AI_TOOLS[toolName];
248
- if (!tool) continue;
249
-
250
- const contextPath = join(r, tool.contextFile);
251
-
252
- // Create config directory if needed
253
- if (tool.configDir) {
254
- mkdirSync(join(r, tool.configDir), { recursive: true });
255
- }
256
-
257
- // Ensure parent directory of context file exists
258
- const parentDir = join(contextPath, "..");
259
- mkdirSync(parentDir, { recursive: true });
260
-
261
- if (!existsSync(contextPath) || force) {
262
- writeFileSync(contextPath, context, "utf-8");
263
- created.push(relative(r, contextPath));
264
- }
265
-
266
- // Install Claude Code skills if it's Claude
267
- if (toolName === "claude-code") {
268
- const skillFiles = installClaudeSkills(r, force);
269
- created.push(...skillFiles);
270
- }
271
- }
272
-
273
- return created;
274
- }
275
-
276
- /**
277
- * Copy Claude Code skill files from the framework's directories.
278
- */
279
- function installClaudeSkills(root: string, force: boolean): string[] {
280
- const created: string[] = [];
281
-
282
- // Determine the framework root (where packages/core/src/ lives)
283
- const thisDir = dirname(fileURLToPath(import.meta.url));
284
- const frameworkRoot = resolve(thisDir, "..", "..", "..");
285
-
286
- // Copy .skill files from the framework's skills/ directory to project root
287
- const skillsSource = join(frameworkRoot, "skills");
288
- if (existsSync(skillsSource)) {
289
- for (const entry of readdirSync(skillsSource)) {
290
- if (entry.endsWith(".skill")) {
291
- const srcFile = join(skillsSource, entry);
292
- const target = join(root, entry);
293
- if (!existsSync(target) || force) {
294
- copyFileSync(srcFile, target);
295
- created.push(entry);
296
- }
297
- }
298
- }
299
- }
300
-
301
- // Copy skill directories from .claude/skills/ in the framework to the project
302
- const frameworkSkillsDir = join(frameworkRoot, ".claude", "skills");
303
- if (existsSync(frameworkSkillsDir)) {
304
- const targetSkillsDir = join(root, ".claude", "skills");
305
- mkdirSync(targetSkillsDir, { recursive: true });
306
- for (const entry of readdirSync(frameworkSkillsDir)) {
307
- const skillDir = join(frameworkSkillsDir, entry);
308
- if (existsSync(skillDir) && statSync(skillDir).isDirectory()) {
309
- const targetDir = join(targetSkillsDir, entry);
310
- if (!existsSync(targetDir) || force) {
311
- cpSync(skillDir, targetDir, { recursive: true, force: true });
312
- created.push(relative(root, targetDir));
313
- }
314
- }
315
- }
316
- }
317
-
318
- return created;
319
- }
320
-
321
- /**
322
- * Install Tina4 context for ALL known AI tools (not just detected ones).
323
- */
324
- export function installAllAiContext(root: string = ".", force: boolean = false): string[] {
325
- return installAiContext(root, {
326
- tools: Object.keys(AI_TOOLS),
327
- force,
328
- });
329
- }
330
-
331
- /**
332
- * Generate a human-readable report of AI tool detection.
333
- */
334
- export function aiStatusReport(root: string = "."): string {
335
- const tools = detectAi(root);
336
- const installed = tools.filter((t) => t.status === "detected");
337
- const missing = tools.filter((t) => t.status === "not-detected");
338
-
339
- const lines: string[] = ["\nTina4 AI Context Status\n"];
340
-
341
- if (installed.length > 0) {
342
- lines.push("Detected AI tools:");
343
- for (const t of installed) {
344
- lines.push(` + ${t.description} (${t.name})`);
345
- }
346
- } else {
347
- lines.push("No AI coding tools detected.");
348
- }
349
-
350
- if (missing.length > 0) {
351
- lines.push("\nNot detected (install context with `tina4nodejs ai --all`):");
352
- for (const t of missing) {
353
- lines.push(` - ${t.description} (${t.name})`);
354
- }
355
- }
356
-
357
- lines.push("");
358
- return lines.join("\n");
359
- }
353
+ export { AiTool as AiToolType };