@skalfa/skalfa-cli 1.0.8 → 1.0.10

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.
@@ -11,13 +11,14 @@ const node_fs_1 = __importDefault(require("node:fs"));
11
11
  const add_extension_1 = require("../commands/add-extension");
12
12
  const create_api_1 = require("../commands/create-api");
13
13
  const create_app_1 = require("../commands/create-app");
14
+ const init_1 = require("../commands/init");
14
15
  const pick_1 = require("../commands/pick");
15
16
  const update_1 = require("../commands/update");
16
17
  const agent_1 = require("../commands/agent");
17
18
  const fs_1 = require("../utils/fs");
18
19
  // Dynamic routing / forwarding logic
19
20
  const args = process.argv.slice(2);
20
- const knownCommands = ["create:api", "create:app", "add", "pick", "update", "agent:install", "agent:update"];
21
+ const knownCommands = ["init", "create:api", "create:app", "add", "pick", "update", "agent:install", "agent:update"];
21
22
  if (args.length > 0 && !knownCommands.includes(args[0]) && !["-h", "--help", "-v", "--version", "help"].includes(args[0])) {
22
23
  const projectRoot = (0, fs_1.findProjectRoot)(process.cwd());
23
24
  if (projectRoot) {
@@ -58,6 +59,13 @@ program
58
59
  .description("Start building with skalfa ecosystem.")
59
60
  .version(version)
60
61
  .addHelpText("before", banner);
62
+ program
63
+ .command("init")
64
+ .description("Initialize a new Skalfa monorepo project containing both API and App.")
65
+ .argument("<name>", "project folder name")
66
+ .action(async (name) => {
67
+ await runCommand(() => (0, init_1.initProject)(name));
68
+ });
61
69
  program
62
70
  .command("create:api")
63
71
  .description("Create a new Skalfa API project.")
@@ -8,11 +8,11 @@ exports.updateAgent = updateAgent;
8
8
  const node_child_process_1 = require("node:child_process");
9
9
  const node_path_1 = __importDefault(require("node:path"));
10
10
  const node_fs_1 = __importDefault(require("node:fs"));
11
- async function installAgent(overrideType) {
11
+ async function installAgent(overrideType, targetProjectDir = process.cwd()) {
12
12
  let type = overrideType;
13
13
  // 1. Auto-detect project type if not overridden
14
14
  if (!type) {
15
- const pkgPath = node_path_1.default.join(process.cwd(), "package.json");
15
+ const pkgPath = node_path_1.default.join(targetProjectDir, "package.json");
16
16
  if (!node_fs_1.default.existsSync(pkgPath)) {
17
17
  throw new Error("package.json not found. Please run this command in your project root.");
18
18
  }
@@ -38,7 +38,7 @@ async function installAgent(overrideType) {
38
38
  if (type !== "api" && type !== "app") {
39
39
  throw new Error("Invalid agent type. Must be 'api' or 'app'.");
40
40
  }
41
- const targetDir = node_path_1.default.join(process.cwd(), ".agents");
41
+ const targetDir = node_path_1.default.join(targetProjectDir, ".agents");
42
42
  if (node_fs_1.default.existsSync(targetDir)) {
43
43
  throw new Error(".agents folder already exists. Run 'skalfa agent:update' to pull the latest changes.");
44
44
  }
@@ -69,7 +69,7 @@ async function installAgent(overrideType) {
69
69
  }
70
70
  }
71
71
  // 3. Add /.agents/ to project's .gitignore
72
- const gitignorePath = node_path_1.default.join(process.cwd(), ".gitignore");
72
+ const gitignorePath = node_path_1.default.join(targetProjectDir, ".gitignore");
73
73
  let gitignoreContent = "";
74
74
  if (node_fs_1.default.existsSync(gitignorePath)) {
75
75
  gitignoreContent = node_fs_1.default.readFileSync(gitignorePath, "utf8");
@@ -84,6 +84,32 @@ async function installAgent(overrideType) {
84
84
  async function updateAgent() {
85
85
  const targetDir = node_path_1.default.join(process.cwd(), ".agents");
86
86
  if (!node_fs_1.default.existsSync(targetDir)) {
87
+ // Check if it's a monorepo
88
+ const apiAgentDir = node_path_1.default.join(process.cwd(), "api", ".agents");
89
+ const appAgentDir = node_path_1.default.join(process.cwd(), "app", ".agents");
90
+ if (node_fs_1.default.existsSync(apiAgentDir) || node_fs_1.default.existsSync(appAgentDir)) {
91
+ if (node_fs_1.default.existsSync(apiAgentDir)) {
92
+ console.log("Updating API agent...");
93
+ try {
94
+ (0, node_child_process_1.execSync)("git pull", { cwd: apiAgentDir, stdio: "inherit" });
95
+ console.log("API Agent updated successfully!");
96
+ }
97
+ catch (e) {
98
+ console.error(`Failed to update API agent: ${e.message}`);
99
+ }
100
+ }
101
+ if (node_fs_1.default.existsSync(appAgentDir)) {
102
+ console.log("Updating APP agent...");
103
+ try {
104
+ (0, node_child_process_1.execSync)("git pull", { cwd: appAgentDir, stdio: "inherit" });
105
+ console.log("APP Agent updated successfully!");
106
+ }
107
+ catch (e) {
108
+ console.error(`Failed to update APP agent: ${e.message}`);
109
+ }
110
+ }
111
+ return;
112
+ }
87
113
  throw new Error("No agent installed in this project. Run 'skalfa agent:install' first.");
88
114
  }
89
115
  const gitDir = node_path_1.default.join(targetDir, ".git");
@@ -34,7 +34,7 @@ class Questioner {
34
34
  this.rl.close();
35
35
  }
36
36
  }
37
- async function createApi(projectName) {
37
+ async function createApi(projectName, options) {
38
38
  const cwd = process.cwd();
39
39
  const target = node_path_1.default.resolve(cwd, projectName);
40
40
  const packageName = node_path_1.default.basename(target);
@@ -42,24 +42,26 @@ async function createApi(projectName) {
42
42
  if ((0, fs_1.exists)(target)) {
43
43
  throw new Error(`Target directory already exists: ${target}`);
44
44
  }
45
- // Ask interactive questions sequentially using a single readline interface
46
- const q = new Questioner();
47
- let hasRedis = false;
48
- let hasQueue = false;
49
- let hasCache = false;
50
- let hasCron = false;
51
- let hasDa = false;
52
- let hasSocket = false;
53
- try {
54
- hasRedis = (await q.ask("Do you need Redis? (y/N): ", "No")).toLowerCase().startsWith("y");
55
- hasQueue = (await q.ask("Do you need Queue? (y/N): ", "No")).toLowerCase().startsWith("y");
56
- hasCache = (await q.ask("Do you need Cache? (y/N): ", "No")).toLowerCase().startsWith("y");
57
- hasCron = (await q.ask("Do you need Cron? (y/N): ", "No")).toLowerCase().startsWith("y");
58
- hasDa = (await q.ask("Do you need Data Analytics? (y/N): ", "No")).toLowerCase().startsWith("y");
59
- hasSocket = (await q.ask("Do you need Socket? (y/N): ", "No")).toLowerCase().startsWith("y");
60
- }
61
- finally {
62
- q.close();
45
+ let hasRedis = options?.redis ?? false;
46
+ let hasQueue = options?.queue ?? false;
47
+ let hasCache = options?.cache ?? false;
48
+ let hasCron = options?.cron ?? false;
49
+ let hasDa = options?.da ?? false;
50
+ let hasSocket = options?.socket ?? false;
51
+ if (!options) {
52
+ // Ask interactive questions sequentially using a single readline interface
53
+ const q = new Questioner();
54
+ try {
55
+ hasRedis = (await q.ask("Do you need Redis? (y/N): ", "No")).toLowerCase().startsWith("y");
56
+ hasQueue = (await q.ask("Do you need Queue? (y/N): ", "No")).toLowerCase().startsWith("y");
57
+ hasCache = (await q.ask("Do you need Cache? (y/N): ", "No")).toLowerCase().startsWith("y");
58
+ hasCron = (await q.ask("Do you need Cron? (y/N): ", "No")).toLowerCase().startsWith("y");
59
+ hasDa = (await q.ask("Do you need Data Analytics? (y/N): ", "No")).toLowerCase().startsWith("y");
60
+ hasSocket = (await q.ask("Do you need Socket? (y/N): ", "No")).toLowerCase().startsWith("y");
61
+ }
62
+ finally {
63
+ q.close();
64
+ }
63
65
  }
64
66
  // Dependency relation: Queue or Cache requires Redis
65
67
  const finalRedis = hasRedis || hasQueue || hasCache;
@@ -83,7 +85,7 @@ async function createApi(projectName) {
83
85
  const tarballUrl = await (0, npm_1.fetchLatestTarballUrl)(templatePackageName);
84
86
  // Create a temporary extraction directory inside parent folder of target
85
87
  const parentDir = node_path_1.default.dirname(target);
86
- const tempExtractDir = node_path_1.default.join(parentDir, `${projectName}-temp-extract`);
88
+ const tempExtractDir = node_path_1.default.join(parentDir, `${packageName}-temp-extract`);
87
89
  if ((0, fs_1.exists)(tempExtractDir)) {
88
90
  node_fs_1.default.rmSync(tempExtractDir, { recursive: true, force: true });
89
91
  }
@@ -121,6 +123,52 @@ async function createApi(projectName) {
121
123
  }
122
124
  }
123
125
  renamePackage(target, packageName);
126
+ // Write template README.md file
127
+ const readmePath = node_path_1.default.join(target, "README.md");
128
+ const readmeContent = `# [Project Name] - Backend Service
129
+
130
+ ## Overview
131
+ - **Description:** [Provide a short description of this project]
132
+ - **What it does:** [Briefly explain what this project does]
133
+ - **Why it exists:** [Explain why this project was created]
134
+ - **Main Goals:** [List the main goals of this service]
135
+ - **Target Users:** [Specify the target users/consumers of this service]
136
+
137
+ ## Skalfa Stack
138
+ This backend service is built on top of **@skalfa/skalfa-api** with the following configured utilities:
139
+ - **Core:** \\\`@skalfa/skalfa-api-core\\\`
140
+ - **Database / ORM:** \\\`@skalfa/skalfa-orm\\\` (Knex)
141
+ - **Redis Connection:** \`${finalRedis ? "Enabled (@skalfa/skalfa-redis)" : "Disabled"}\`
142
+ - **Queue Worker:** \`${hasQueue ? "Enabled (@skalfa/skalfa-queue)" : "Disabled"}\`
143
+ - **Cache Manager:** \`${hasCache ? "Enabled (@skalfa/skalfa-cache)" : "Disabled"}\`
144
+ - **Cron Scheduler:** \`${hasCron ? "Enabled (@skalfa/skalfa-cron)" : "Disabled"}\`
145
+ - **Data Analytics:** \`${hasDa ? "Enabled (@skalfa/skalfa-da / ClickHouse)" : "Disabled"}\`
146
+ - **WebSocket Server:** \`${hasSocket ? "Enabled (@skalfa/skalfa-socket)" : "Disabled"}\`
147
+
148
+ ## Features & Status
149
+ Here is the list of features and their development status:
150
+
151
+ | Feature | Description | Status |
152
+ | :--- | :--- | :---: |
153
+ | **Login** | API Login | \\\`[x] Completed\\\` |
154
+
155
+ ## Development Setup
156
+
157
+ ### Prerequisites
158
+ Ensure you have [Bun](https://bun.sh) or [Node.js](https://nodejs.org) installed.
159
+
160
+ ### Commands
161
+ | Task | Bun | npm |
162
+ | :--- | :--- | :--- |
163
+ | **Install Dependencies** | \\\`bun install\\\` | \\\`npm install\\\` |
164
+ | **Start Dev Server** | \\\`bun run dev\\\` | \\\`npm run dev\\\` |
165
+ | **Build Production** | \\\`bun run build\\\` | \\\`npm run build\\\` |
166
+ | **Start Production** | \\\`bun run start\\\` | \\\`npm run start\\\` |
167
+
168
+ ## Agent Instructions
169
+ If you are an AI coding agent assisting with this project, please make sure to read the workspace instructions and guidelines located in the [API Agent Rules](file:///.agents/AGENTS.md) or the root monorepo [Agent Rules](file:///../.agents/AGENTS.md) folder before making modifications.
170
+ `;
171
+ node_fs_1.default.writeFileSync(readmePath, readmeContent, "utf8");
124
172
  // Rename .npmignore to .gitignore if it exists (npm renames .gitignore to .npmignore during pack/publish)
125
173
  const npmignorePath = node_path_1.default.join(target, ".npmignore");
126
174
  const gitignorePath = node_path_1.default.join(target, ".gitignore");
@@ -33,7 +33,7 @@ class Questioner {
33
33
  this.rl.close();
34
34
  }
35
35
  }
36
- async function createApp(projectName) {
36
+ async function createApp(projectName, options) {
37
37
  const cwd = process.cwd();
38
38
  const target = node_path_1.default.resolve(cwd, projectName);
39
39
  const packageName = node_path_1.default.basename(target);
@@ -41,24 +41,26 @@ async function createApp(projectName) {
41
41
  if ((0, fs_1.exists)(target)) {
42
42
  throw new Error(`Target directory already exists: ${target}`);
43
43
  }
44
- // Ask interactive questions sequentially
45
- const q = new Questioner();
46
- let hasIdb = false;
47
- let hasSocket = false;
48
- let hasDocument = false;
49
- let hasPwa = false;
50
- let hasTauriDesktop = false;
51
- let hasTauriMobile = false;
52
- try {
53
- hasIdb = (await q.ask("Do you need IndexedDB (IDB)? (y/N): ", "No")).toLowerCase().startsWith("y");
54
- hasSocket = (await q.ask("Do you need Socket Client? (y/N): ", "No")).toLowerCase().startsWith("y");
55
- hasDocument = (await q.ask("Do you need Document Export/Viewer (PDF/Excel)? (y/N): ", "No")).toLowerCase().startsWith("y");
56
- hasPwa = (await q.ask("Do you want to enable Progressive Web App (PWA)? (y/N): ", "No")).toLowerCase().startsWith("y");
57
- hasTauriDesktop = (await q.ask("Do you want to enable Tauri Desktop support (Windows/macOS/Linux)? (y/N): ", "No")).toLowerCase().startsWith("y");
58
- hasTauriMobile = (await q.ask("Do you want to enable Tauri Mobile support (Android/iOS)? (y/N): ", "No")).toLowerCase().startsWith("y");
59
- }
60
- finally {
61
- q.close();
44
+ let hasIdb = options?.idb ?? false;
45
+ let hasSocket = options?.socket ?? false;
46
+ let hasDocument = options?.document ?? false;
47
+ let hasPwa = options?.pwa ?? false;
48
+ let hasTauriDesktop = options?.tauriDesktop ?? false;
49
+ let hasTauriMobile = options?.tauriMobile ?? false;
50
+ if (!options) {
51
+ // Ask interactive questions sequentially
52
+ const q = new Questioner();
53
+ try {
54
+ hasIdb = (await q.ask("Do you need IndexedDB (IDB)? (y/N): ", "No")).toLowerCase().startsWith("y");
55
+ hasSocket = (await q.ask("Do you need Socket Client? (y/N): ", "No")).toLowerCase().startsWith("y");
56
+ hasDocument = (await q.ask("Do you need Document Export/Viewer (PDF/Excel)? (y/N): ", "No")).toLowerCase().startsWith("y");
57
+ hasPwa = (await q.ask("Do you want to enable Progressive Web App (PWA)? (y/N): ", "No")).toLowerCase().startsWith("y");
58
+ hasTauriDesktop = (await q.ask("Do you want to enable Tauri Desktop support (Windows/macOS/Linux)? (y/N): ", "No")).toLowerCase().startsWith("y");
59
+ hasTauriMobile = (await q.ask("Do you want to enable Tauri Mobile support (Android/iOS)? (y/N): ", "No")).toLowerCase().startsWith("y");
60
+ }
61
+ finally {
62
+ q.close();
63
+ }
62
64
  }
63
65
  const spinner = new spinner_1.Spinner("Preparing project...");
64
66
  spinner.start();
@@ -79,7 +81,7 @@ async function createApp(projectName) {
79
81
  spinner.update(`Fetching latest template info for ${templatePackageName}...`);
80
82
  const tarballUrl = await (0, npm_1.fetchLatestTarballUrl)(templatePackageName);
81
83
  const parentDir = node_path_1.default.dirname(target);
82
- const tempExtractDir = node_path_1.default.join(parentDir, `${projectName}-temp-extract`);
84
+ const tempExtractDir = node_path_1.default.join(parentDir, `${packageName}-temp-extract`);
83
85
  if ((0, fs_1.exists)(tempExtractDir)) {
84
86
  node_fs_1.default.rmSync(tempExtractDir, { recursive: true, force: true });
85
87
  }
@@ -115,6 +117,50 @@ async function createApp(projectName) {
115
117
  }
116
118
  }
117
119
  renamePackage(target, packageName);
120
+ // Write template README.md file
121
+ const readmePath = node_path_1.default.join(target, "README.md");
122
+ const readmeContent = `# [Project Name] - Frontend Application
123
+
124
+ ## Overview
125
+ - **Description:** [Provide a short description of this project]
126
+ - **What it does:** [Briefly explain what this project does]
127
+ - **Why it exists:** [Explain why this project was created]
128
+ - **Main Goals:** [List the main goals of this application]
129
+ - **Target Users:** [Specify the target users of this application]
130
+
131
+ ## Skalfa Stack
132
+ This frontend application is built on top of **@skalfa/skalfa-app** with the following configured utilities:
133
+ - **Core:** \\\`@skalfa/skalfa-app-core\\\`
134
+ - **IndexedDB Storage:** \`${hasIdb ? "Enabled (@skalfa/skalfa-idb)" : "Disabled"}\`
135
+ - **Socket Client:** \`${hasSocket ? "Enabled (@skalfa/skalfa-socket-client)" : "Disabled"}\`
136
+ - **Document Export:** \`${hasDocument ? "Enabled (@skalfa/skalfa-document)" : "Disabled"}\`
137
+ - **PWA Support:** \`${hasPwa ? "Enabled (@ducanh2912/next-pwa)" : "Disabled"}\`
138
+ - **Tauri Support:** \`${hasTauriDesktop || hasTauriMobile ? `Enabled (@tauri-apps/api) - ${[hasTauriDesktop ? "Desktop" : "", hasTauriMobile ? "Mobile" : ""].filter(Boolean).join("/")}` : "Disabled"}\`
139
+
140
+ ## Features & Status
141
+ Here is the list of features and their development status:
142
+
143
+ | Feature | Description | Status |
144
+ | :--- | :--- | :---: |
145
+ | **Login** | Login pages | \\\`[x] Completed\\\` |
146
+
147
+ ## Development Setup
148
+
149
+ ### Prerequisites
150
+ Make sure you have [Bun](https://bun.sh) or [Node.js](https://nodejs.org) installed.
151
+
152
+ ### Commands
153
+ | Task | Bun | npm |
154
+ | :--- | :--- | :--- |
155
+ | **Install Dependencies** | \\\`bun install\\\` | \\\`npm install\\\` |
156
+ | **Start Dev Server** | \\\`bun run dev\\\` | \\\`npm run dev\\\` |
157
+ | **Build Production** | \\\`bun run build\\\` | \\\`npm run build\\\` |
158
+ | **Start Production** | \\\`bun run start\\\` | \\\`npm run start\\\` |
159
+
160
+ ## Agent Instructions
161
+ If you are an AI coding agent assisting with this project, please make sure to read the workspace instructions and guidelines located in the [App Agent Rules](file:///.agents/AGENTS.md) or the root monorepo [Agent Rules](file:///../.agents/AGENTS.md) folder before making modifications.
162
+ `;
163
+ node_fs_1.default.writeFileSync(readmePath, readmeContent, "utf8");
118
164
  // Rename .npmignore to .gitignore if it exists
119
165
  const npmignorePath = node_path_1.default.join(target, ".npmignore");
120
166
  const gitignorePath = node_path_1.default.join(target, ".gitignore");
@@ -0,0 +1,175 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.initProject = initProject;
7
+ const node_path_1 = __importDefault(require("node:path"));
8
+ const node_fs_1 = __importDefault(require("node:fs"));
9
+ const node_readline_1 = __importDefault(require("node:readline"));
10
+ const create_api_1 = require("./create-api");
11
+ const create_app_1 = require("./create-app");
12
+ const agent_1 = require("./agent");
13
+ const fs_1 = require("../utils/fs");
14
+ class Questioner {
15
+ rl;
16
+ constructor() {
17
+ this.rl = node_readline_1.default.createInterface({
18
+ input: process.stdin,
19
+ output: process.stdout,
20
+ });
21
+ }
22
+ ask(query, defaultValue = "") {
23
+ return new Promise((resolve) => {
24
+ this.rl.question(query, (answer) => {
25
+ resolve(answer.trim() || defaultValue);
26
+ });
27
+ });
28
+ }
29
+ close() {
30
+ this.rl.close();
31
+ }
32
+ }
33
+ async function initProject(projectName) {
34
+ const cwd = process.cwd();
35
+ const target = node_path_1.default.resolve(cwd, projectName);
36
+ (0, fs_1.assertInsideDirectory)(cwd, target);
37
+ if ((0, fs_1.exists)(target)) {
38
+ throw new Error(`Target directory already exists: ${target}`);
39
+ }
40
+ // Ask interactive questions sequentially
41
+ const q = new Questioner();
42
+ // API Options
43
+ let apiRedis = false;
44
+ let apiQueue = false;
45
+ let apiCache = false;
46
+ let apiCron = false;
47
+ let apiDa = false;
48
+ let apiSocket = false;
49
+ // APP Options
50
+ let appIdb = false;
51
+ let appSocket = false;
52
+ let appDocument = false;
53
+ let appPwa = false;
54
+ let appTauriDesktop = false;
55
+ let appTauriMobile = false;
56
+ try {
57
+ console.log("\n--- Configure API (Backend) ---");
58
+ apiRedis = (await q.ask("Do you need Redis? (y/N): ", "No")).toLowerCase().startsWith("y");
59
+ apiQueue = (await q.ask("Do you need Queue? (y/N): ", "No")).toLowerCase().startsWith("y");
60
+ apiCache = (await q.ask("Do you need Cache? (y/N): ", "No")).toLowerCase().startsWith("y");
61
+ apiCron = (await q.ask("Do you need Cron? (y/N): ", "No")).toLowerCase().startsWith("y");
62
+ apiDa = (await q.ask("Do you need Data Analytics? (y/N): ", "No")).toLowerCase().startsWith("y");
63
+ apiSocket = (await q.ask("Do you need Socket? (y/N): ", "No")).toLowerCase().startsWith("y");
64
+ console.log("\n--- Configure App (Frontend) ---");
65
+ appIdb = (await q.ask("Do you need IndexedDB (IDB)? (y/N): ", "No")).toLowerCase().startsWith("y");
66
+ appSocket = (await q.ask("Do you need Socket Client? (y/N): ", "No")).toLowerCase().startsWith("y");
67
+ appDocument = (await q.ask("Do you need Document Export/Viewer (PDF/Excel)? (y/N): ", "No")).toLowerCase().startsWith("y");
68
+ appPwa = (await q.ask("Do you want to enable Progressive Web App (PWA)? (y/N): ", "No")).toLowerCase().startsWith("y");
69
+ appTauriDesktop = (await q.ask("Do you want to enable Tauri Desktop support (Windows/macOS/Linux)? (y/N): ", "No")).toLowerCase().startsWith("y");
70
+ appTauriMobile = (await q.ask("Do you want to enable Tauri Mobile support (Android/iOS)? (y/N): ", "No")).toLowerCase().startsWith("y");
71
+ }
72
+ finally {
73
+ q.close();
74
+ }
75
+ console.log(`\nInitializing Skalfa project in ${target}...`);
76
+ node_fs_1.default.mkdirSync(target, { recursive: true });
77
+ const apiDir = node_path_1.default.join(target, "api");
78
+ const appDir = node_path_1.default.join(target, "app");
79
+ console.log("\nCreating API...");
80
+ await (0, create_api_1.createApi)(apiDir, {
81
+ redis: apiRedis,
82
+ queue: apiQueue,
83
+ cache: apiCache,
84
+ cron: apiCron,
85
+ da: apiDa,
86
+ socket: apiSocket,
87
+ });
88
+ console.log("\nCreating App...");
89
+ await (0, create_app_1.createApp)(appDir, {
90
+ idb: appIdb,
91
+ socket: appSocket,
92
+ document: appDocument,
93
+ pwa: appPwa,
94
+ tauriDesktop: appTauriDesktop,
95
+ tauriMobile: appTauriMobile,
96
+ });
97
+ console.log("\nInstalling AI Agents...");
98
+ try {
99
+ await (0, agent_1.installAgent)("api", apiDir);
100
+ }
101
+ catch (err) {
102
+ console.error(`Failed to install API agent: ${err.message}`);
103
+ }
104
+ try {
105
+ await (0, agent_1.installAgent)("app", appDir);
106
+ }
107
+ catch (err) {
108
+ console.error(`Failed to install App agent: ${err.message}`);
109
+ }
110
+ console.log("\nConfiguring root files...");
111
+ // 1. Root package.json
112
+ const rootPackageJson = {
113
+ name: projectName,
114
+ private: true,
115
+ workspaces: [
116
+ "api",
117
+ "app"
118
+ ]
119
+ };
120
+ node_fs_1.default.writeFileSync(node_path_1.default.join(target, "package.json"), JSON.stringify(rootPackageJson, null, 2), "utf8");
121
+ // 2. Root .gitignore
122
+ const rootGitignore = `node_modules/
123
+ .agents/
124
+ `;
125
+ node_fs_1.default.writeFileSync(node_path_1.default.join(target, ".gitignore"), rootGitignore, "utf8");
126
+ // 3. Root README.md
127
+ const rootReadme = `# ${projectName}
128
+
129
+ This is a Skalfa monorepo project containing both the backend (API) and the frontend (App).
130
+
131
+ ## Structure
132
+ - \`api/\` - Backend service (Elysia, Knex, etc.)
133
+ - \`app/\` - Frontend application (Next.js)
134
+
135
+ ## Getting Started
136
+
137
+ To install dependencies for both projects:
138
+ \`\`\`bash
139
+ bun install
140
+ \`\`\`
141
+
142
+ To run the development servers:
143
+ - **API**: \`cd api && bun run dev\`
144
+ - **App**: \`cd app && bun run dev\`
145
+ `;
146
+ node_fs_1.default.writeFileSync(node_path_1.default.join(target, "README.md"), rootReadme, "utf8");
147
+ // 4. Root .agents folder
148
+ const agentsDir = node_path_1.default.join(target, ".agents");
149
+ node_fs_1.default.mkdirSync(agentsDir, { recursive: true });
150
+ // 5. Root .agents/skills.json
151
+ const skillsJson = {
152
+ entries: [
153
+ { path: "../api/.agents" },
154
+ { path: "../app/.agents" }
155
+ ]
156
+ };
157
+ node_fs_1.default.writeFileSync(node_path_1.default.join(agentsDir, "skills.json"), JSON.stringify(skillsJson, null, 2), "utf8");
158
+ // 6. Root .agents/AGENTS.md
159
+ const agentsMd = `# Monorepo Rules
160
+
161
+ This is a combined Skalfa project containing both the backend (\`api\`) and frontend (\`app\`).
162
+
163
+ ## Project Structure
164
+ - **Backend (API)**: Located in the [api](file:///api) directory.
165
+ - **Frontend (APP)**: Located in the [app](file:///app) directory.
166
+
167
+ ## Instructions
168
+ 1. When working on backend features, APIs, database migrations, or models, make changes inside the [api](file:///api) directory. Refer to [api/.agents](file:///api/.agents) for backend-specific guidelines.
169
+ 2. When working on frontend features, pages, components, or state management, make changes inside the [app](file:///app) directory. Refer to [app/.agents](file:///app/.agents) for frontend-specific guidelines.
170
+ 3. Always ensure that API contracts and communication between the frontend and backend are aligned.
171
+ `;
172
+ node_fs_1.default.writeFileSync(node_path_1.default.join(agentsDir, "AGENTS.md"), agentsMd, "utf8");
173
+ console.log(`\nSuccessfully initialized ${projectName}!`);
174
+ console.log(`\nNext steps:\n cd ${projectName}\n bun install\n bun run --cwd api dev (or cd api && bun run dev)\n bun run --cwd app dev (or cd app && bun run dev)`);
175
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@skalfa/skalfa-cli",
3
- "version": "1.0.8",
3
+ "version": "1.0.10",
4
4
  "description": "Command Line Interface tool for scaffolding Skalfa projects, managing extensions, and ejecting core utilities.",
5
5
  "main": "dist/bin/skalfa.js",
6
6
  "bin": {