tina4-nodejs 3.10.34 → 3.10.40
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 +1 -1
- package/packages/cli/src/bin.ts +13 -26
- package/packages/cli/src/commands/seed.ts +72 -0
- package/packages/cli/src/commands/serve.ts +2 -1
- package/packages/core/src/ai.ts +241 -247
- package/packages/core/src/devAdmin.ts +289 -6
- package/packages/core/src/index.ts +3 -3
- package/packages/core/src/metrics.ts +800 -0
- package/packages/core/src/response.ts +98 -40
- package/packages/core/src/router.ts +5 -0
- package/packages/core/src/server.ts +3 -8
- package/packages/core/src/types.ts +2 -2
- package/packages/orm/src/baseModel.ts +25 -0
- package/packages/orm/src/database.ts +38 -0
package/packages/core/src/ai.ts
CHANGED
|
@@ -1,137 +1,229 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Tina4 AI —
|
|
2
|
+
* Tina4 AI — Install AI coding assistant context files.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
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 {
|
|
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,
|
|
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
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
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
|
-
|
|
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
|
-
*
|
|
140
|
+
* Install context file for a single tool.
|
|
107
141
|
*/
|
|
108
|
-
|
|
109
|
-
const
|
|
110
|
-
const
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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
|
|
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
|
-
*
|
|
196
|
+
* Copy Claude Code skill files from the framework's directories.
|
|
126
197
|
*/
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
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
|
|
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/
|
|
158
|
-
src/
|
|
159
|
-
src/
|
|
160
|
-
src/
|
|
161
|
-
src/
|
|
162
|
-
|
|
163
|
-
|
|
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/
|
|
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 };
|