odac 1.2.0 → 1.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 (55) hide show
  1. package/.agent/rules/memory.md +16 -1
  2. package/.github/workflows/release.yml +27 -5
  3. package/.husky/pre-push +3 -3
  4. package/.releaserc.js +2 -2
  5. package/AGENTS.md +47 -0
  6. package/CHANGELOG.md +64 -0
  7. package/README.md +1 -1
  8. package/bin/odac.js +187 -8
  9. package/client/odac.js +243 -178
  10. package/docs/ai/README.md +49 -0
  11. package/docs/ai/skills/SKILL.md +39 -0
  12. package/docs/ai/skills/backend/authentication.md +67 -0
  13. package/docs/ai/skills/backend/config.md +32 -0
  14. package/docs/ai/skills/backend/controllers.md +62 -0
  15. package/docs/ai/skills/backend/cron.md +50 -0
  16. package/docs/ai/skills/backend/database.md +21 -0
  17. package/docs/ai/skills/backend/forms.md +19 -0
  18. package/docs/ai/skills/backend/ipc.md +55 -0
  19. package/docs/ai/skills/backend/mail.md +34 -0
  20. package/docs/ai/skills/backend/request_response.md +35 -0
  21. package/docs/ai/skills/backend/routing.md +51 -0
  22. package/docs/ai/skills/backend/storage.md +43 -0
  23. package/docs/ai/skills/backend/streaming.md +34 -0
  24. package/docs/ai/skills/backend/structure.md +57 -0
  25. package/docs/ai/skills/backend/translations.md +42 -0
  26. package/docs/ai/skills/backend/utilities.md +24 -0
  27. package/docs/ai/skills/backend/validation.md +53 -0
  28. package/docs/ai/skills/backend/views.md +61 -0
  29. package/docs/ai/skills/frontend/core.md +66 -0
  30. package/docs/ai/skills/frontend/forms.md +21 -0
  31. package/docs/ai/skills/frontend/navigation.md +20 -0
  32. package/docs/ai/skills/frontend/realtime.md +47 -0
  33. package/docs/backend/04-routing/09-websocket.md +14 -1
  34. package/docs/backend/10-authentication/01-user-logins-with-authjs.md +2 -0
  35. package/docs/backend/10-authentication/05-session-management.md +25 -3
  36. package/package.json +13 -13
  37. package/src/Auth.js +100 -15
  38. package/src/Database.js +1 -1
  39. package/src/Mail.js +19 -9
  40. package/src/Odac.js +17 -14
  41. package/src/Request.js +5 -1
  42. package/src/Route/Internal.js +21 -18
  43. package/src/Route/MimeTypes.js +56 -0
  44. package/src/Route.js +136 -92
  45. package/src/Validator.js +23 -14
  46. package/src/View/Form.js +91 -51
  47. package/src/View.js +15 -10
  48. package/src/WebSocket.js +45 -12
  49. package/test/Auth.test.js +249 -0
  50. package/test/Client.test.js +29 -0
  51. package/test/Odac.test.js +4 -2
  52. package/test/Route.test.js +104 -0
  53. package/test/View/Form.test.js +37 -0
  54. package/test/WebSocket.test.js +141 -3
  55. package/.github/workflows/test-publish.yml +0 -36
@@ -28,6 +28,21 @@ trigger: always_on
28
28
  - **Loop Optimization:** Use labeled loops (`label: for`) for efficient control flow in nested structures. Eliminate intermediate "flag" variables (`isMatch`, `found`) by using direct `return` or `continue label`.
29
29
  - **Direct Returns:** Return a value as soon as it is determined. Avoid assigning to a temporary variable (e.g. `matchedUser`) and breaking the loop, unless post-loop processing is strictly necessary.
30
30
  - **Async State Safety:** When an async function depends on mutable class state (like `pendingMiddlewares`), capture that state into a local `const` *synchronously* before triggering any async operations. This prevents race conditions where the state changes before the async task consumes it.
31
+ - **Async I/O Preference:** Prefer asynchronous file system operations (`fs.promises` or `await fs.promises.*`) over synchronous methods (`fs.readFileSync`, `fs.writeFileSync`) to prevent blocking the event loop and ensure high concurrency, especially in request handling paths.
31
32
 
32
33
  ## Dependency Management
33
- - **Prefer Native Fetch:** Use the native `fetch` API for network requests in both Node.js (18+) and browser environments to reduce dependencies and bundle size.
34
+ - **Prefer Native Fetch:** Use the native `fetch` API for network requests in both Node.js (18+) and browser environments to reduce dependencies and bundle size.
35
+
36
+ ## Naming & Text Conventions
37
+ - **ODAC Casing:** Always write "ODAC" in uppercase letters when referring to the framework name in strings, comments, log messages, or user-facing text. **EXCEPTION:** The class name itself (`class Odac`) and variable references to it should remain `Odac` (PascalCase) as per code conventions.
38
+
39
+ ## Testing & Validation
40
+ - **Mandatory Test Coverage:** Every new feature, method, or significant logic change MUST be accompanied by a corresponding unit or integration test.
41
+ - **Verify Correctness:** do not assume code works; prove it with a test that covers both success and failure scenarios (e.g., edge cases, error conditions).
42
+ - **Update Existing Tests:** If a feature modifies existing behavior, update the relevant tests to reflect the new logic and ensure they pass.
43
+
44
+ ## Client Library (odac.js)
45
+ - **Automatic JSON Parsing:** The `#ajax` method (and by extension `odac.get`) must automatically parse the response if the `Content-Type` header contains `application/json`, even if `dataType` is not explicitly set to `json`.
46
+
47
+ ## Security Logic & Authentication
48
+ - **Enterprise Token Rotation:** The `Auth.js` system utilizes a non-blocking refresh token rotation mechanism for cookies (`odac_x`/`odac_y`). To prevent race conditions during concurrent requests in high-throughput SPAs, rotated tokens are **not** immediately deleted. Instead, their `active` timestamp is set to naturally expire in 60 seconds (Grace Period), and their `date` timestamp is set to the Unix Epoch (`new Date(0)`) as an identifier mark. Never delete rotated tokens immediately.
@@ -7,7 +7,7 @@ on:
7
7
  paths-ignore:
8
8
  - 'CHANGELOG.md'
9
9
  - 'package.json'
10
- - '.github/workflows/test-publish.yml'
10
+ - '.github/workflows/**'
11
11
  workflow_dispatch:
12
12
 
13
13
  jobs:
@@ -41,10 +41,6 @@ jobs:
41
41
  env:
42
42
  GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
43
43
  run: npx semantic-release
44
-
45
- - name: Publish to npm
46
- run: npm publish --provenance --access public
47
-
48
44
  - name: Get version
49
45
  id: version
50
46
  run: echo "version=$(node -p "require('./package.json').version")" >> $GITHUB_OUTPUT
@@ -56,6 +52,8 @@ jobs:
56
52
  repository: odac-run/actions
57
53
  token: ${{ secrets.GH_PAT }}
58
54
  path: .github/odac-actions
55
+ ref: main
56
+ persist-credentials: false
59
57
 
60
58
  - name: Generate Release Notes
61
59
  id: ai_notes
@@ -70,11 +68,35 @@ jobs:
70
68
  - name: Cleanup Actions
71
69
  if: always()
72
70
  run: rm -rf .github/odac-actions
71
+
72
+ - name: Update PR Title
73
+ uses: actions/github-script@v7
74
+ if: steps.ai_notes.outcome == 'success'
75
+ env:
76
+ AI_TITLE: ${{ steps.ai_notes.outputs.release-title }}
77
+ with:
78
+ github-token: ${{ secrets.GITHUB_TOKEN }}
79
+ script: |
80
+ const prs = await github.rest.repos.listPullRequestsAssociatedWithCommit({
81
+ owner: context.repo.owner,
82
+ repo: context.repo.repo,
83
+ commit_sha: context.sha,
84
+ });
85
+ if (prs.data.length > 0) {
86
+ await github.rest.pulls.update({
87
+ owner: context.repo.owner,
88
+ repo: context.repo.repo,
89
+ pull_number: prs.data[0].number,
90
+ title: process.env.AI_TITLE
91
+ });
92
+ }
93
+
73
94
  - name: Update Release
74
95
  uses: softprops/action-gh-release@v1
75
96
  if: steps.ai_notes.outcome == 'success' && steps.version.outputs.version != ''
76
97
  with:
77
98
  tag_name: v${{ steps.version.outputs.version }}
99
+ name: ${{ steps.ai_notes.outputs.release-title }}
78
100
  body_path: RELEASE_NOTE.md
79
101
  draft: false
80
102
  prerelease: false
package/.husky/pre-push CHANGED
@@ -1,10 +1,10 @@
1
1
  #!/usr/bin/env sh
2
2
  . "$(dirname -- "$0")/_/husky.sh"
3
3
 
4
- # Prevent pushing code with High/Critical vulnerabilities
5
- echo "šŸ›”ļø Running Security Gate (npm audit)..."
4
+ # Prevent pushing code with High/Critical vulnerabilities in production dependencies
5
+ echo "šŸ›”ļø Running Production Security Gate (npm audit)..."
6
6
 
7
- npm audit --audit-level=high
7
+ npm audit --audit-level=high --omit=dev
8
8
 
9
9
  if [ $? -ne 0 ]; then
10
10
  echo "āŒ Security Check Failed! High vulnerabilities detected."
package/.releaserc.js CHANGED
@@ -122,7 +122,7 @@ Powered by [⚔ ODAC](https://odac.run)
122
122
  [
123
123
  '@semantic-release/npm',
124
124
  {
125
- npmPublish: false
125
+ provenance: true
126
126
  }
127
127
  ],
128
128
  [
@@ -134,4 +134,4 @@ Powered by [⚔ ODAC](https://odac.run)
134
134
  ],
135
135
  '@semantic-release/github'
136
136
  ]
137
- }
137
+ }
package/AGENTS.md ADDED
@@ -0,0 +1,47 @@
1
+ # ODAC Agent Instructions
2
+
3
+ You are an AI Agent operating within the **ODAC Framework** repository. This document outlines the core principles, architectural standards, and operational guidelines you MUST follow to maintain the integrity and performance of this enterprise-grade system.
4
+
5
+ ## 1. Project Identity & Philosophy
6
+ - **Name:** ODAC (Always uppercase in strings/docs/logs).
7
+ - **Core Goal:** To provide a robust, zero-config, high-performance Node.js framework for distributed cloud applications.
8
+ - **The "Big 3" Priorities:**
9
+ 1. **Enterprise-Level Security:** Security is foundational. Default to secure, validate all inputs, sanitize all outputs.
10
+ 2. **Zero-Config:** Works out-of-the-box. Convention over configuration.
11
+ 3. **High Performance:** Optimize for throughput and low latency (Sub-millisecond targets).
12
+
13
+ ## 2. Architectural Principles
14
+ - **Asynchronous & Non-Blocking:** Exclusively use non-blocking I/O. Use `fs.promises` instead of sync methods.
15
+ - **Dependency Injection (DI):** Build components with DI for maximum testability.
16
+ - **Single Responsibility Principle (SRP):** Keep classes and functions focused and small.
17
+ - **Memory Management:** Be paranoid about leaks. Clean up listeners, streams, and connections.
18
+ - **O(n log n) Bound:** Prioritize O(1) or O(n log n) algorithms. Justify any O(n²) operations.
19
+
20
+ ## 3. Coding Standards & Integrity
21
+ - **Modern JavaScript:** Use ES6+ features, ES Modules (import/export).
22
+ - **Strictly Prohibited:** **No usage of `var`**. Use `const` (preferred) or `let`.
23
+ - **Fail-Fast Pattern:** Implement early returns for negative cases. Avoid deeply nested `if/else`.
24
+ - **Anti-Spaghetti Rules:**
25
+ - Resolve Promises upfront (e.g., `Promise.all`) before loops.
26
+ - Avoid mixing `await` inside deep logic.
27
+ - Capture mutable state synchronously before async operations.
28
+ - **No Quick/Lazy Fixes:** Implement correctly from the start. Refactor if necessary; no "band-aid" patches.
29
+
30
+ ## 4. Technical Constraints (Strict Compliance)
31
+ - **Session Safety:** `Odac.Request.setSession()` MUST be called before accessing `Odac.Request.session()`.
32
+ - **Structured Logging:** No `console.log`. Use the internal JSON logger with appropriate levels.
33
+ - **Native APIs:** Prefer native Node.js/Browser APIs (like `fetch`) over external libraries to minimize overhead.
34
+ - **Token Rotation:** In `Auth.js`, use the 60-second grace period for rotated tokens. Never delete them immediately.
35
+ - **Ajax Parsing:** `odac.js` must automatically parse JSON responses if headers allow.
36
+
37
+ ## 5. Testing & Documentation
38
+ - **TDD Requirement:** No feature is complete without unit/integration tests covering both success and edge cases.
39
+ - **Documentation:** Every exported member must have JSDoc explaining *Why* it exists, not just *What* it does.
40
+ - **No User Dialogues in Code:** Do not include assistant-user interaction in comments or code files.
41
+
42
+ ## 6. Communication Style
43
+ - **Authoritative & Precise:** Be the expert. Do not explain basic concepts.
44
+ - **Proactive Correction:** If the user suggests a sub-optimal or insecure pattern (e.g., synchronous reads), refuse and implement the correct async version, explaining the trade-off.
45
+
46
+ ---
47
+ *Note: This file is a living document. Updates should be reflected in `memory.md` and subsequent AI interactions.*
package/CHANGELOG.md CHANGED
@@ -1,3 +1,67 @@
1
+ ### āš™ļø Engine Tuning
2
+
3
+ - Extract MIME type definitions into a dedicated module.
4
+
5
+ ### āš”ļø Performance Upgrades
6
+
7
+ - prevent redundant database table migration calls by introducing a static cache to track completed migrations.
8
+
9
+ ### ✨ What's New
10
+
11
+ - add comprehensive ODAC Agent instructions and guidelines
12
+ - **auth:** implement enterprise refresh token rotation with grace period and session persistence
13
+ - Automatically parse JSON responses in client-side AJAX requests based on the `Content-Type` header.
14
+ - implement comprehensive AI agent skills system and automated CLI setup
15
+
16
+ ### šŸ› ļø Fixes & Improvements
17
+
18
+ - add HTML escaping functionality to Form class and corresponding tests
19
+ - **ai:** correct target path for syncing AI skills
20
+ - **auth:** replace magic number with constant for rotated token threshold
21
+ - **auth:** replace magic number with constant for token rotation grace period
22
+ - enhance odac:form parser with nested quotes and dynamic binding support
23
+ - **route:** support nested property paths in actions and resolve App class conflict
24
+ - **view:** ensure odac:for with 'in' attribute parses correctly as javascript
25
+ - **view:** implement clear attribute in odac:form to control auto-clearing
26
+
27
+
28
+
29
+ ---
30
+
31
+ Powered by [⚔ ODAC](https://odac.run)
32
+
33
+ ### āš™ļø Engine Tuning
34
+
35
+ - Improve disposable domain cache management by relocating the cache path, ensuring directory existence, and standardizing error logging.
36
+ - Migrate file system operations in Mail and View to use async `fs.promises` for non-blocking I/O, aligning with new memory.md guidelines.
37
+ - remove unused `WebSocketClient` variable assignments in `WebSocket.test.js`
38
+ - streamline cache file reading by removing redundant access check.
39
+ - **validator:** Migrate file operations to `fs.promises` for asynchronous I/O and enhance security with explicit content sanitization.
40
+
41
+ ### āš”ļø Performance Upgrades
42
+
43
+ - Optimize parametric route matching by implementing a two-phase, pre-indexed lookup strategy grouped by segment count.
44
+
45
+ ### ✨ What's New
46
+
47
+ - Add configurable max payload size and message rate limiting options to WebSocket routes.
48
+ - Refined pre-push security audit to omit dev dependencies, optimized variable initializations, enhanced test mocks with `writeHead` and `finished` properties, introduced conditional `setTimeout` for request handling, and improved code formatting in client-side WebSocket and AJAX logic.
49
+ - update `.gitignore` to no longer ignore package manager lock files and to ignore the `storage/` directory
50
+
51
+ ### šŸ› ļø Fixes & Improvements
52
+
53
+ - Enhance route directory not found error logging and add ODAC casing convention to memory rules.
54
+ - Set owner-only read/write permissions (0o600) for temporary cache files created during validation.
55
+ - suppress tailwind process stdout output
56
+ - Update default Unix socket path and enhance socket connection error handling with specific guidance for `ENOENT` errors.
57
+ - Update Tailwind CSS watch flag to '--watch=always' and refine child process stdio configuration.
58
+
59
+
60
+
61
+ ---
62
+
63
+ Powered by [⚔ ODAC](https://odac.run)
64
+
1
65
  ### agent
2
66
 
3
67
  - Clarify logging strategies for development and production environments
package/README.md CHANGED
@@ -10,7 +10,7 @@
10
10
  * šŸ”— **Powerful Routing:** Create clean, custom URLs and manage infinite pages with a flexible routing system.
11
11
  * ✨ **Seamless SPA Experience:** Automatic AJAX handling for forms and page transitions eliminates the need for complex client-side code.
12
12
  * šŸ›”ļø **Built-in Security:** Automatic CSRF protection and secure default headers keep your application safe.
13
- * šŸ” **Authentication:** Ready-to-use session management, password hashing, and authentication helpers.
13
+ * šŸ” **Authentication:** Ready-to-use session management with enterprise-grade **Refresh Token Rotation**, secure password hashing, and authentication helpers.
14
14
  * šŸ—„ļø **Database Agnostic:** Integrated support for major databases (PostgreSQL, MySQL, SQLite) and Redis via Knex.js.
15
15
  * šŸŒ **i18n Support:** Native multi-language support to help you reach a global audience.
16
16
  * ā° **Task Scheduling:** Built-in Cron job system for handling background tasks and recurring operations.
package/bin/odac.js CHANGED
@@ -16,6 +16,72 @@ const rl = readline.createInterface({
16
16
 
17
17
  const ask = question => new Promise(resolve => rl.question(question, answer => resolve(answer.trim())))
18
18
 
19
+ /**
20
+ * Interactive selection menu for CLI.
21
+ * @param {string} title Menu title
22
+ * @param {string[]} options List of choice strings
23
+ * @returns {Promise<number>} Selected index
24
+ */
25
+ const select = async (title, options) => {
26
+ if (!process.stdout.isTTY) return 0
27
+
28
+ return new Promise(resolve => {
29
+ let current = 0
30
+ const hideCursor = '\u001B[?25l'
31
+ const showCursor = '\u001B[?25h'
32
+
33
+ // Calculate total lines the title occupies
34
+ const titleLines = title.split('\n')
35
+ const totalLines = titleLines.length + options.length
36
+
37
+ const render = () => {
38
+ // Clear all lines we previously wrote
39
+ titleLines.forEach(line => {
40
+ process.stdout.write('\r\x1b[K' + line + '\n')
41
+ })
42
+ options.forEach((opt, i) => {
43
+ const line = i === current ? `\x1b[36m āÆ ${opt}\x1b[0m` : ` ${opt}`
44
+ process.stdout.write('\r\x1b[K' + line + '\n')
45
+ })
46
+ process.stdout.write(`\x1b[${totalLines}A`) // Move back to the very start
47
+ }
48
+
49
+ process.stdout.write(hideCursor)
50
+ if (!process.stdin.isRaw) {
51
+ process.stdin.setRawMode(true)
52
+ process.stdin.resume()
53
+ }
54
+ readline.emitKeypressEvents(process.stdin)
55
+
56
+ render()
57
+
58
+ const onKey = (str, key) => {
59
+ if (key.name === 'up') {
60
+ current = current > 0 ? current - 1 : options.length - 1
61
+ render()
62
+ } else if (key.name === 'down') {
63
+ current = current < options.length - 1 ? current + 1 : 0
64
+ render()
65
+ } else if (key.name === 'return' || key.name === 'enter') {
66
+ cleanup()
67
+ process.stdout.write(`\x1b[${totalLines}B\n`) // Move down past everything
68
+ resolve(current)
69
+ } else if (key.ctrl && key.name === 'c') {
70
+ cleanup()
71
+ process.exit()
72
+ }
73
+ }
74
+
75
+ const cleanup = () => {
76
+ process.stdin.removeListener('keypress', onKey)
77
+ if (process.stdin.isRaw) process.stdin.setRawMode(false)
78
+ process.stdout.write(showCursor)
79
+ }
80
+
81
+ process.stdin.on('keypress', onKey)
82
+ })
83
+ }
84
+
19
85
  /**
20
86
  * Resolves Tailwind CSS paths and ensures required directories/files exist.
21
87
  * Supports multiple CSS entry points from 'view/css'.
@@ -73,6 +139,94 @@ function getTailwindConfigs() {
73
139
  return configs
74
140
  }
75
141
 
142
+ /**
143
+ * Manages the AI Agent skills synchronization.
144
+ * @param {string} targetDir The directory to sync skills into.
145
+ */
146
+ async function manageSkills(targetDir = process.cwd()) {
147
+ const aiSourceDir = path.resolve(__dirname, '../docs/ai')
148
+
149
+ if (!fs.existsSync(aiSourceDir)) {
150
+ console.error('āŒ AI components not found in framework.')
151
+ return
152
+ }
153
+
154
+ const options = [
155
+ 'Antigravity / Cascade (.agent/skills)',
156
+ 'Claude / Projects (.claude/skills)',
157
+ 'Continue (.continue/skills)',
158
+ 'Cursor (.cursor/skills)',
159
+ 'Kilo Code (.kilocode/skills)',
160
+ 'Kiro CLI (.kiro/skills)',
161
+ 'Qwen Code (.qwen/skills)',
162
+ 'Windsurf (.windsurf/skills)',
163
+ 'Custom Path',
164
+ 'Skip / Cancel'
165
+ ]
166
+
167
+ const choiceIndex = await select('\nšŸ¤– \x1b[36mODAC AI Agent Skills Manager\x1b[0m\nSelect your AI Agent / IDE for setup:', options)
168
+
169
+ let targetSubDir = ''
170
+ let copySkillsOnly = true
171
+
172
+ const SKIP_INDEX = 9
173
+ const CUSTOM_INDEX = 8
174
+
175
+ if (choiceIndex === SKIP_INDEX) return // Skip / Cancel
176
+
177
+ switch (choiceIndex) {
178
+ case 0:
179
+ targetSubDir = '.agent/skills'
180
+ break
181
+ case 1:
182
+ targetSubDir = '.claude/skills'
183
+ break
184
+ case 2:
185
+ targetSubDir = '.continue/skills'
186
+ break
187
+ case 3:
188
+ targetSubDir = '.cursor/skills'
189
+ break
190
+ case 4:
191
+ targetSubDir = '.kilocode/skills'
192
+ break
193
+ case 5:
194
+ targetSubDir = '.kiro/skills'
195
+ break
196
+ case 6:
197
+ targetSubDir = '.qwen/skills'
198
+ break
199
+ case 7:
200
+ targetSubDir = '.windsurf/skills'
201
+ break
202
+ case CUSTOM_INDEX:
203
+ targetSubDir = await ask('Enter custom path: ')
204
+ copySkillsOnly = false
205
+ break
206
+ default:
207
+ return
208
+ }
209
+
210
+ const targetBase = path.resolve(targetDir, targetSubDir)
211
+ const targetPath = targetBase
212
+
213
+ try {
214
+ fs.mkdirSync(targetPath, {recursive: true})
215
+
216
+ if (copySkillsOnly) {
217
+ const skillsSource = path.join(aiSourceDir, 'skills')
218
+ fs.cpSync(skillsSource, targetPath, {recursive: true})
219
+ } else {
220
+ fs.cpSync(aiSourceDir, targetPath, {recursive: true})
221
+ }
222
+
223
+ console.log(`\n✨ AI skills successfully synced to: \x1b[32m${targetSubDir}\x1b[0m`)
224
+ console.log('Your AI Agent now has full knowledge of the ODAC Framework. šŸš€')
225
+ } catch (err) {
226
+ console.error('āŒ Failed to sync AI skills:', err.message)
227
+ }
228
+ }
229
+
76
230
  async function run() {
77
231
  if (command === 'init') {
78
232
  const projectName = args[0] || '.'
@@ -110,13 +264,27 @@ async function run() {
110
264
  }
111
265
 
112
266
  console.log('\n✨ Project initialized successfully!')
267
+
268
+ // Interactive AI Skills setup
269
+ if (process.stdout.isTTY) {
270
+ const setupAIIndex = await select('\nšŸ¤– Should we setup AI Agent skills for your IDE?', ['Yes', 'No'])
271
+ if (setupAIIndex === 0) {
272
+ await manageSkills(targetDir)
273
+ } else {
274
+ console.log('\nšŸ’” \x1b[33mTip:\x1b[0m You can always run \x1b[36mnpx odac skills\x1b[0m later.')
275
+ }
276
+ }
113
277
  } catch (error) {
114
278
  console.error('āŒ Error initializing project:', error.message)
115
279
  }
116
280
  } else if (command === 'dev') {
281
+ // ... existing dev logic ...
117
282
  if (cluster.isPrimary) {
118
283
  const configs = getTailwindConfigs()
119
284
  const tails = []
285
+ const names = configs.map(c => c.name).join(', ')
286
+
287
+ console.log(`šŸŽØ \x1b[36mODAC Styles:\x1b[0m Watching for changes (${names})`)
120
288
 
121
289
  configs.forEach(({input, cssOutput, name, isCustom}) => {
122
290
  let tailwindProcess = null
@@ -125,19 +293,28 @@ async function run() {
125
293
  const localCli = path.join(process.cwd(), 'node_modules', '.bin', 'tailwindcss')
126
294
  const useLocal = fs.existsSync(localCli)
127
295
  const cmd = useLocal ? localCli : 'npx'
128
- const args = useLocal ? ['-i', input, '-o', cssOutput, '--watch'] : ['@tailwindcss/cli', '-i', input, '-o', cssOutput, '--watch']
129
-
130
- console.log(`šŸŽØ Starting Tailwind CSS for ${name} (${isCustom ? 'Custom' : 'Default'})...`)
131
- console.log(`šŸ“‚ Watching directory: ${process.cwd()}`)
296
+ const args = useLocal
297
+ ? ['-i', input, '-o', cssOutput, '--watch=always']
298
+ : ['@tailwindcss/cli', '-i', input, '-o', cssOutput, '--watch=always']
132
299
 
133
300
  tailwindProcess = spawn(cmd, args, {
134
- stdio: 'inherit',
135
- shell: !useLocal, // Valid for npm/npx compatibility if local not found
301
+ stdio: ['pipe', 'ignore', 'pipe'],
302
+ shell: !useLocal,
136
303
  cwd: process.cwd()
137
304
  })
138
305
 
306
+ tailwindProcess.stderr.on('data', chunk => {
307
+ const raw = chunk.toString()
308
+ const lines = raw.split('\n')
309
+ for (const line of lines) {
310
+ const clean = line.replace(/\x1B\[[0-9;]*[JKmsu]/g, '').trim()
311
+ if (!clean || clean.startsWith('Done in') || clean.startsWith('ā‰ˆ')) continue
312
+ process.stderr.write(`\x1b[31m[ODAC Style Error]\x1b[0m ${line}\n`)
313
+ }
314
+ })
315
+
139
316
  tailwindProcess.on('error', err => {
140
- console.error(`āŒ Tailwind watcher failed to start for ${name}:`, err.message)
317
+ console.error(`āŒ \x1b[31m[ODAC Style Error]\x1b[0m Failed to start watcher for ${name}:`, err.message)
141
318
  })
142
319
 
143
320
  tailwindProcess.on('exit', code => {
@@ -150,7 +327,6 @@ async function run() {
150
327
 
151
328
  startWatcher()
152
329
 
153
- // Push a wrapper compatible with the cleanup function
154
330
  tails.push({
155
331
  kill: () => {
156
332
  if (tailwindProcess) tailwindProcess.kill()
@@ -198,6 +374,8 @@ async function run() {
198
374
  } else if (command === 'start') {
199
375
  process.env.NODE_ENV = 'production'
200
376
  require('../index.js')
377
+ } else if (command === 'skills') {
378
+ await manageSkills()
201
379
  } else {
202
380
  console.log('Usage:')
203
381
  console.log(' npx odac init (Interactive mode)')
@@ -205,6 +383,7 @@ async function run() {
205
383
  console.log(' npx odac dev (Development mode)')
206
384
  console.log(' npx odac build (Production build)')
207
385
  console.log(' npx odac start (Start server)')
386
+ console.log(' npx odac skills (Sync AI Agent skills)')
208
387
  }
209
388
 
210
389
  rl.close()