@maccesar/titools 2.2.9 → 2.2.11

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.
@@ -42,6 +42,49 @@ function hasAnySkillSymlink(platformSkillsDir) {
42
42
  return skillList.some((skill) => existsSync(join(platformSkillsDir, skill)));
43
43
  }
44
44
 
45
+ /**
46
+ * Perform the actual update for a specific scope
47
+ * @param {string|undefined} baseDir - Base directory (undefined = global, path = local)
48
+ * @param {string} repoDir - Repository directory
49
+ * @param {Object} spinner - Ora spinner instance
50
+ * @returns {Promise<void>}
51
+ */
52
+ async function performUpdate(baseDir, repoDir, spinner) {
53
+ // Detect platforms with existing symlinks (only update those)
54
+ const detectedPlatforms = detectPlatforms(baseDir);
55
+ const platformsWithSymlinks = detectedPlatforms.filter((p) =>
56
+ hasAnySkillSymlink(p.skillsDir)
57
+ );
58
+
59
+ // Install skills
60
+ spinner.start('Syncing skills...');
61
+ const skillsResult = await installSkills(repoDir, baseDir);
62
+ spinner.succeed(`${skillsResult.installed.length} skills updated`);
63
+
64
+ // Install agents (only if Claude Code has symlinks)
65
+ const claudePlatform = platformsWithSymlinks.find((p) => p.name === 'claude');
66
+ if (claudePlatform) {
67
+ spinner.start('Syncing agents...');
68
+ const agentsResult = await installAgents(repoDir, baseDir);
69
+ if (agentsResult.installed.length > 0) {
70
+ spinner.succeed('Platform agents updated');
71
+ } else {
72
+ spinner.info('No agents to sync');
73
+ }
74
+ }
75
+
76
+ cleanupLegacyArtifacts(baseDir);
77
+
78
+ // Update symlinks silently for platforms that already had them
79
+ for (const platform of platformsWithSymlinks) {
80
+ await createSkillSymlinks(
81
+ platform.skillsDir,
82
+ SKILLS,
83
+ baseDir
84
+ );
85
+ }
86
+ }
87
+
45
88
  /**
46
89
  * Update command handler
47
90
  * @param {Object} options - Command options
@@ -65,15 +108,16 @@ export async function updateCommand(options) {
65
108
  const localPlatforms = detectPlatforms(projectDir);
66
109
  const hasLocalSkills = isProject && hasSkillsAt(projectDir);
67
110
  const hasGlobalSkills = hasSkillsAt(undefined);
68
- if (hasLocalSkills && !hasGlobalSkills) {
69
- baseDir = projectDir;
70
- } else if (isProject && localPlatforms.length > 0) {
111
+
112
+ // Only show prompt if BOTH local and global skills exist
113
+ if (hasLocalSkills && hasGlobalSkills) {
71
114
  try {
72
115
  const scope = await select({
73
- message: 'Local installation detected. What do you want to update:',
116
+ message: 'Both local and global skills detected. What do you want to update:',
74
117
  choices: [
75
118
  { name: 'Global skills (user home)', value: 'global' },
76
119
  { name: 'Local skills (current project)', value: 'local' },
120
+ { name: 'Both locations', value: 'both' },
77
121
  ],
78
122
  theme: {
79
123
  style: {
@@ -87,32 +131,44 @@ export async function updateCommand(options) {
87
131
  process.exit(0);
88
132
  }
89
133
  if (scope === 'local') {
90
- baseDir = process.cwd();
134
+ baseDir = projectDir;
135
+ } else if (scope === 'both') {
136
+ baseDir = 'both';
91
137
  }
92
138
  } catch (error) {
93
139
  console.log('\nCancelled.');
94
140
  process.exit(0);
95
141
  }
142
+ } else if (hasLocalSkills && !hasGlobalSkills) {
143
+ // Only local skills exist, update local
144
+ baseDir = projectDir;
96
145
  }
146
+ // If only global skills exist, baseDir remains undefined (global)
97
147
  }
98
148
 
99
- if (baseDir) {
149
+ // Display mode
150
+ if (baseDir === 'both') {
151
+ console.log(chalk.cyan('Mode: Updating both global and local skills'));
152
+ } else if (baseDir) {
100
153
  console.log(chalk.cyan('Mode: Local update (current project)'));
101
154
  } else {
102
155
  console.log(chalk.cyan('Mode: Global update (user home)'));
103
156
  }
104
157
  console.log('');
105
158
 
106
- const skillsDir = getAgentsSkillsDir(baseDir);
107
- const hasSkillsInstalled = skillsDir && SKILLS.some((skill) => existsSync(join(skillsDir, skill)));
108
- if (!hasSkillsInstalled) {
109
- console.log(chalk.yellow('No skills installed at this location.'));
110
- console.log('Install them first with:');
111
- console.log(' titools install');
112
- console.log('');
113
- console.log('Looked for skills in:');
114
- console.log(` ${baseDir ? 'Local' : 'Global'}: ${skillsDir}`);
115
- return;
159
+ // Verify skills are installed
160
+ if (baseDir !== 'both') {
161
+ const skillsDir = getAgentsSkillsDir(baseDir);
162
+ const hasSkillsInstalled = skillsDir && SKILLS.some((skill) => existsSync(join(skillsDir, skill)));
163
+ if (!hasSkillsInstalled) {
164
+ console.log(chalk.yellow('No skills installed at this location.'));
165
+ console.log('Install them first with:');
166
+ console.log(' titools install');
167
+ console.log('');
168
+ console.log('Looked for skills in:');
169
+ console.log(` ${baseDir ? 'Local' : 'Global'}: ${skillsDir}`);
170
+ return;
171
+ }
116
172
  }
117
173
 
118
174
  // Check for updates
@@ -121,7 +177,7 @@ export async function updateCommand(options) {
121
177
  try {
122
178
  const hasUpdate = await checkForUpdate(PACKAGE_VERSION);
123
179
 
124
- // If there's a newer version on GitHub, prompt user to update CLI first
180
+ // If there's a newer version on npm, prompt user to update CLI first
125
181
  if (hasUpdate) {
126
182
  let latestVersion = '(newer)';
127
183
  try {
@@ -158,38 +214,18 @@ export async function updateCommand(options) {
158
214
  return;
159
215
  }
160
216
 
161
- // Detect platforms with existing symlinks (only update those)
162
- const detectedPlatforms = detectPlatforms(baseDir);
163
- const platformsWithSymlinks = detectedPlatforms.filter((p) =>
164
- hasAnySkillSymlink(p.skillsDir)
165
- );
166
-
167
- // Install skills
168
- spinner.start('Syncing skills...');
169
- const skillsResult = await installSkills(repoDir, baseDir);
170
- spinner.succeed(`${skillsResult.installed.length} skills updated`);
171
-
172
- // Install agents (only if Claude Code has symlinks)
173
- const claudePlatform = platformsWithSymlinks.find((p) => p.name === 'claude');
174
- if (claudePlatform) {
175
- spinner.start('Syncing agents...');
176
- const agentsResult = await installAgents(repoDir, baseDir);
177
- if (agentsResult.installed.length > 0) {
178
- spinner.succeed('Platform agents updated');
179
- } else {
180
- spinner.info('No agents to sync');
181
- }
182
- }
183
-
184
- cleanupLegacyArtifacts(baseDir);
217
+ // Perform update(s)
218
+ if (baseDir === 'both') {
219
+ // Update both global and local
220
+ console.log(chalk.bold('Updating global skills...'));
221
+ await performUpdate(undefined, repoDir, spinner);
222
+ console.log('');
185
223
 
186
- // Update symlinks silently for platforms that already had them
187
- for (const platform of platformsWithSymlinks) {
188
- await createSkillSymlinks(
189
- platform.skillsDir,
190
- SKILLS,
191
- baseDir
192
- );
224
+ console.log(chalk.bold('Updating local skills...'));
225
+ await performUpdate(process.cwd(), repoDir, spinner);
226
+ } else {
227
+ // Update single scope
228
+ await performUpdate(baseDir, repoDir, spinner);
193
229
  }
194
230
 
195
231
  // Summary
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@maccesar/titools",
3
- "version": "2.2.9",
3
+ "version": "2.2.11",
4
4
  "description": "Titanium SDK skills and agents for AI coding assistants (Claude Code, Gemini CLI, Codex CLI)",
5
5
  "main": "lib/index.js",
6
6
  "type": "module",
@@ -31,18 +31,25 @@ Behavior:
31
31
 
32
32
  ## Workflow
33
33
 
34
- 1. Architecture: define structure (`lib/api`, `lib/services`, `lib/helpers`)
34
+ 1. Architecture: define structure by technical type with flat folders (`lib/api`, `lib/services`, `lib/actions`, `lib/repositories`, `lib/helpers`)
35
35
  2. Data strategy: choose Models (SQLite) or Collections (API)
36
36
  3. Contracts: define I/O specs between layers
37
37
  4. Implementation: write XML views and ES6+ controllers
38
38
  5. Quality: testing, error handling, logging, performance
39
39
  6. Cleanup: implement a `cleanup()` pattern for memory management
40
40
 
41
+ ## Organization policy (low freedom)
42
+
43
+ - Use technical-type organization in `lib` (for example: `api`, `services`, `actions`, `repositories`, `helpers`, `policies`, `providers`).
44
+ - Keep `lib` flat and predictable: `lib/<type>/<file>.js` only.
45
+ - Do not recommend deep nesting like `lib/services/auth/session/login.js`.
46
+ - Keep UI layers aligned by screen (`controllers/`, `views/`, `styles/`) and avoid unnecessary depth.
47
+
41
48
  ## Quick start example
42
49
 
43
50
  Minimal example that matches the conventions:
44
51
 
45
- View (`views/user/card.xml`)
52
+ View (`views/userCard.xml`)
46
53
  ```xml
47
54
  <Alloy>
48
55
  <View id="cardContainer">
@@ -58,7 +65,7 @@ View (`views/user/card.xml`)
58
65
  </Alloy>
59
66
  ```
60
67
 
61
- Styles (`styles/user/card.tss`)
68
+ Styles (`styles/userCard.tss`)
62
69
  ```tss
63
70
  "#cardContainer": { left: 8, right: 8, top: 8, height: Ti.UI.SIZE, borderRadius: 12, backgroundColor: '#fff' }
64
71
  "#headerRow": { layout: 'horizontal', left: 12, right: 12, top: 12, height: Ti.UI.SIZE, width: Ti.UI.FILL }
@@ -67,7 +74,7 @@ Styles (`styles/user/card.tss`)
67
74
  "#viewProfileBtn": { left: 12, right: 12, bottom: 12, height: 40, width: Ti.UI.FILL, borderRadius: 6, backgroundColor: '#2563eb', color: '#fff' }
68
75
  ```
69
76
 
70
- Controller (`controllers/user/card.js`)
77
+ Controller (`controllers/userCard.js`)
71
78
  ```javascript
72
79
  const { Navigation } = require('services/navigation')
73
80
 
@@ -77,7 +84,7 @@ function init() {
77
84
  }
78
85
 
79
86
  function onViewProfile() {
80
- Navigation.open('user/profile', { userId: $.args.user.id })
87
+ Navigation.open('userProfile', { userId: $.args.user.id })
81
88
  }
82
89
 
83
90
  function cleanup() {
@@ -163,6 +170,7 @@ For the complete reference with examples, see [Alloy builtins and globals](refer
163
170
  | Fastest way to build? | `tn <recipe>` (using TiNy CLI wrapper) |
164
171
  | Where does API call go? | `lib/api/` |
165
172
  | Where does business logic go? | `lib/services/` |
173
+ | How deep should `lib` folders be? | One level: `lib/<type>/<file>.js` |
166
174
  | Where do I store auth tokens? | Keychain (iOS) / KeyStore (Android) via service |
167
175
  | Models or Collections? | Collections for API data, Models for SQLite persistence |
168
176
  | Ti.App.fireEvent or EventBus? | Always EventBus (Backbone.Events) |
@@ -4,52 +4,81 @@
4
4
 
5
5
  ```
6
6
  app/
7
- ├── controllers/ # View orchestrators
7
+ ├── controllers/ # View orchestrators
8
8
  │ ├── index.js # Bootstrap only (no business logic)
9
- └── feature/
10
- └── list.js # Controller for list view
9
+ ├── home.js
10
+ └── userProfile.js
11
11
  ├── models/ # OPTIONAL: For persistence with migrations
12
12
  │ └── user.js # Model definition (ONLY if using SQLite)
13
13
  ├── views/ # XML views
14
14
  │ ├── index.xml
15
- └── feature/
16
- └── list.xml # View definition
15
+ ├── home.xml
16
+ └── userProfile.xml
17
17
  ├── styles/ # TSS styles (one per view + global)
18
18
  │ ├── app.tss # Global application styles
19
19
  │ ├── index.tss # Styles for index view
20
- └── feature/
21
- └── list.tss # Styles for feature/list view
20
+ ├── home.tss
21
+ └── userProfile.tss
22
22
  ├── lib/ # Reusable logic (no UI)
23
23
  │ ├── api/
24
- │ │ └── client.js # API calls
24
+ │ │ ├── authApi.js
25
+ │ │ ├── userApi.js
26
+ │ │ └── frameApi.js
25
27
  │ ├── services/
26
- │ │ ├── auth.js # Business logic services
27
- │ │ ├── navigation.js # Navigation orchestration
28
- │ │ └── nativeService.js # Native module wrapper (e.g. Audio, FB, Maps)
29
- └── helpers/
30
- ├── utils.js # Pure utility functions
31
- └── i18n.js # Complex string transformations
28
+ │ │ ├── authService.js
29
+ │ │ ├── navigationService.js
30
+ │ │ └── notificationService.js
31
+ ├── actions/
32
+ ├── syncUserAction.js
33
+ └── refreshSessionAction.js
34
+ │ ├── repositories/
35
+ │ │ ├── userRepository.js
36
+ │ │ └── settingsRepository.js
37
+ │ ├── helpers/
38
+ │ │ ├── validator.js
39
+ │ │ ├── formatter.js
40
+ │ │ └── dateHelper.js
41
+ │ ├── policies/
42
+ │ │ ├── permissionPolicy.js
43
+ │ │ └── featurePolicy.js
44
+ │ └── providers/
45
+ │ ├── containerProvider.js
46
+ │ └── loggerProvider.js
32
47
  ├── widgets/ # Truly reusable components (used in 3+ places)
33
48
  │ └── customButton/
34
49
  ├── config.json # Alloy configuration
35
50
  └── alloy.js # Collections & Global services
36
51
  ```
37
52
 
53
+ ## Organization strategy
54
+
55
+ - `lib/` uses technical-type grouping (Laravel-style naming adapted to Titanium).
56
+ - UI stays in Alloy MVC folders (`controllers`, `views`, `styles`).
57
+ - This is a hybrid approach: technical grouping for reusable logic + screen-based organization for UI files.
58
+ - Keep folder depth low to preserve discoverability.
59
+ - Use clear composed names (`authService.js`, `userRepository.js`, `authApi.js`) and keep multiple files per folder as the normal case.
60
+
61
+ ### Folder depth policy (critical)
62
+
63
+ - Allowed in `lib`: `lib/<type>/<file>.js`
64
+ - Avoid in `lib`: `lib/<type>/<domain>/<subdomain>/<file>.js`
65
+ - If a folder grows too much, split by new technical type, not deep tree nesting.
66
+
38
67
  ## lib/ folder and module require paths
39
68
 
40
69
  :::danger CRITICAL: lib/ Folder is FLATTENED During Build
41
70
  When Alloy compiles, the **entire `lib/` folder is flattened to the root of Resources**. This means:
42
- - `app/lib/services/picsum.js` → `Resources/iphone/services/picsum.js`
43
- - `app/lib/api/client.js` → `Resources/iphone/api/client.js`
71
+ - `app/lib/services/authService.js` → `Resources/iphone/services/authService.js`
72
+ - `app/lib/api/authApi.js` → `Resources/iphone/api/authApi.js`
44
73
 
45
74
  **Therefore, require statements should NOT include `lib/` prefix:**
46
75
  ```javascript
47
76
  // ❌ WRONG - Will fail at runtime
48
- const client = require('lib/api/client')
77
+ const authApi = require('lib/api/authApi')
49
78
 
50
79
  // ✅ CORRECT - Path relative to flattened lib/
51
- const client = require('api/client')
52
- const picsum = require('services/picsum')
80
+ const authApi = require('api/authApi')
81
+ const authService = require('services/authService')
53
82
  ```
54
83
 
55
84
  **This applies to:**
@@ -62,13 +91,18 @@ const picsum = require('services/picsum')
62
91
  app/
63
92
  ├── lib/
64
93
  │ ├── services/
65
- │ │ ├── picsum.js # require('services/logger')
66
- │ │ ├── navigation.js # require('services/logger')
67
- │ │ └── logger.js
68
- └── api/
69
- └── client.js # require('services/logger')
94
+ │ │ ├── authService.js # require('services/navigationService')
95
+ │ │ ├── navigationService.js # require('services/notificationService')
96
+ │ │ └── notificationService.js
97
+ ├── api/
98
+ │ ├── authApi.js # require('services/authService')
99
+ │ │ ├── userApi.js
100
+ │ │ └── frameApi.js
101
+ │ └── repositories/
102
+ │ ├── userRepository.js
103
+ │ └── settingsRepository.js
70
104
  ├── controllers/
71
- │ └── index.js # require('services/picsum')
105
+ │ └── index.js # require('services/authService')
72
106
  ```
73
107
  :::
74
108
 
@@ -145,6 +179,7 @@ api.getFrames()
145
179
  - Handle UI events and delegate to services.
146
180
  - Format data for display (simple cases).
147
181
  - Manage view lifecycle (including cleanup).
182
+ - Keep `lib` modules flat and easy to locate.
148
183
 
149
184
  **DON'T:**
150
185
  - Use inline style attributes in XML (define in TSS files).
@@ -198,3 +198,176 @@ Installation method:
198
198
  2. Open Xcode, then Window, then Devices.
199
199
  3. Select the device, then Installed Apps, then +.
200
200
  4. Select the IPA file.
201
+
202
+ ---
203
+
204
+ ## Mac Catalyst distribution (Mac App Store)
205
+
206
+ Mac Catalyst allows you to run your iPad app on macOS. Titanium SDK 13.1.1.GA and later supports building for Mac Catalyst.
207
+
208
+ ### 1. Enable Mac Catalyst for your App ID
209
+
210
+ 1. Go to [Apple Developer → Identifiers](https://developer.apple.com/account/resources/identifiers/list)
211
+ 2. Select your App ID or create a new one
212
+ 3. Enable **Mac Catalyst** capability
213
+ 4. Save the changes
214
+
215
+ ### 2. Create Mac App Store Distribution Certificate
216
+
217
+ 1. Go to [Apple Developer → Certificates](https://developer.apple.com/account/resources/certificates/list)
218
+ 2. Click **+** to create a new certificate
219
+ 3. Select **Mac App Store Distribution**
220
+ 4. Upload your CSR (Certificate Signing Request)
221
+ 5. Download and install the certificate
222
+
223
+ ### 3. Mac Catalyst build targets
224
+
225
+ Titanium provides two targets for Mac Catalyst:
226
+
227
+ | Target | Description | Configuration |
228
+ |--------|-------------|---------------|
229
+ | `macos` | Development builds for testing on Mac | Debug-maccatalyst |
230
+ | `dist-macappstore` | Production builds for Mac App Store | Release-maccatalyst |
231
+
232
+ ### 4. Build for Mac Catalyst (Development)
233
+
234
+ ```bash
235
+ ti build -p ios -T macos
236
+ ```
237
+
238
+ The `.app` bundle will be created at:
239
+ ```
240
+ build/iphone/build/Products/Debug-maccatalyst/AppName.app
241
+ ```
242
+
243
+ For a production-ready build:
244
+ ```bash
245
+ ti build -p ios -T macos --deploy-type production
246
+ ```
247
+
248
+ The `.app` bundle will be at:
249
+ ```
250
+ build/iphone/build/Products/Release-maccatalyst/AppName.app
251
+ ```
252
+
253
+ ### 5. Build for Mac App Store (Distribution)
254
+
255
+ ```bash
256
+ ti build -p ios -T dist-macappstore [-R <CERTIFICATE_NAME>]
257
+ ```
258
+
259
+ Example:
260
+ ```bash
261
+ ti build -p ios -T dist-macappstore -R "Apple Distribution: Your Team Name (TEAM_ID)"
262
+ ```
263
+
264
+ If you omit the `-R` flag, Titanium will prompt you to select a certificate.
265
+
266
+ **What happens during the build:**
267
+ - Uses `Release-maccatalyst` configuration
268
+ - Sets code signing to Manual with identity `-`
269
+ - Creates a `.xcarchive` for Mac App Store
270
+ - Installs the archive in Xcode's Organizer
271
+ - Destination: `generic/platform=macOS`
272
+
273
+ ### 6. Upload to Mac App Store Connect
274
+
275
+ 1. Open Xcode → Window → Organizer
276
+ 2. Select your Mac Catalyst archive
277
+ 3. Click **Validate App** to check for issues
278
+ 4. Click **Distribute App**
279
+ 5. Select **Mac App Store**
280
+ 6. Follow the prompts to upload
281
+
282
+ ### 7. Create app listing in App Store Connect
283
+
284
+ 1. Go to [App Store Connect](https://appstoreconnect.apple.com)
285
+ 2. **My Apps → + → New App**
286
+ 3. Select **Mac** as platform
287
+ 4. Enter app details:
288
+ - Name
289
+ - Primary Language
290
+ - Bundle ID (must match your App ID with Mac Catalyst enabled)
291
+ - SKU
292
+ 5. Complete required metadata:
293
+ - Description
294
+ - Keywords
295
+ - Screenshots (Mac-specific sizes)
296
+ - Category
297
+ - Age rating
298
+
299
+ ### 8. Mac Catalyst entitlements
300
+
301
+ Add Mac-specific entitlements in `tiapp.xml`:
302
+
303
+ ```xml
304
+ <ios>
305
+ <entitlements>
306
+ <dict>
307
+ <!-- File access for saving to Downloads -->
308
+ <key>com.apple.security.files.user-selected.read-write</key>
309
+ <true/>
310
+ <key>com.apple.security.files.downloads.read-write</key>
311
+ <true/>
312
+
313
+ <!-- App sandbox (required for Mac App Store) -->
314
+ <key>com.apple.security.app-sandbox</key>
315
+ <true/>
316
+
317
+ <!-- Network access -->
318
+ <key>com.apple.security.network.client</key>
319
+ <true/>
320
+
321
+ <!-- Additional entitlements as needed -->
322
+ <key>com.apple.security.print</key>
323
+ <true/>
324
+ </dict>
325
+ </entitlements>
326
+ </ios>
327
+ ```
328
+
329
+ ### 9. Common issues
330
+
331
+ **Issue**: "No suitable signing certificate found"
332
+ - Ensure you have a **Mac App Store Distribution Certificate** (not iOS Distribution)
333
+ - The certificate must be installed in your Keychain
334
+
335
+ **Issue**: Build fails with code signing errors
336
+ - Verify your App ID has Mac Catalyst enabled
337
+ - Check that the certificate matches the App ID
338
+ - Try cleaning the build: `ti clean -p ios`
339
+
340
+ **Issue**: App crashes on launch
341
+ - Verify entitlements are correctly configured
342
+ - Check Console.app for crash logs
343
+ - Ensure all required capabilities are enabled
344
+
345
+ ### 10. Versioning
346
+
347
+ Update version numbers in `tiapp.xml`:
348
+
349
+ ```xml
350
+ <ti:app xmlns:ti="http://ti.tidev.io">
351
+ <id>com.yourcompany.yourapp</id>
352
+ <name>Your App Name</name>
353
+ <version>1.0.0</version>
354
+ <publisher>Your Company</publisher>
355
+ ...
356
+ </ti:app>
357
+ ```
358
+
359
+ - `version`: Display version (e.g., "1.0.0")
360
+ - For iOS, use `pv-version-code` in `<ios>` section for build number
361
+ - For Mac, the build number can be set in Xcode or via `CFBundleVersion`
362
+
363
+ ### 11. Testing on Mac
364
+
365
+ Before submitting to Mac App Store:
366
+
367
+ 1. Build with `macos` target for testing
368
+ 2. Run the app on different Mac architectures (Intel and Apple Silicon)
369
+ 3. Test all features that use file system, network, and other sandboxed resources
370
+ 4. Verify entitlements are working correctly
371
+ 5. Test on macOS versions you plan to support
372
+
373
+ ---
@@ -258,7 +258,7 @@ Omit `-V` and `-P` to be prompted.
258
258
  | `-P, --pp-uuid <uuid>` | Provisioning profile UUID (for device/dist targets). |
259
259
  | `-R, --distribution-name <name>` | iOS Distribution Certificate (for dist targets). |
260
260
  | `--sim-focus` | Focus the iOS Simulator after launching (default: true). Use --no-sim-focus to disable. |
261
- | `-T, --target <value>` | Target: simulator, device, dist-appstore, or dist-adhoc. |
261
+ | `-T, --target <value>` | Target: simulator, device, dist-appstore, dist-adhoc, macos, or dist-macappstore. |
262
262
  | `-V, --developer-name <name>` | iOS Developer Certificate (required for device target). |
263
263
  | `-W, --watch-device-id <udid>` | Watch simulator UDID (simulator only). |
264
264
  | `--watch-app-name <name>` | Name of the watch app to launch (simulator only). |
@@ -394,6 +394,48 @@ ti build -p ios -T dist-appstore -R "Pseudo, Inc." -P "AAAAAAAA-0000-9999-8888-7
394
394
 
395
395
  Installs the package to Xcode Organizer.
396
396
 
397
+ ### Mac Catalyst (macOS Development)
398
+
399
+ ```bash
400
+ ti build -p ios -T macos
401
+ ```
402
+
403
+ Builds a Mac Catalyst version of your iOS app for local testing. The resulting `.app` bundle is located at:
404
+ ```
405
+ build/iphone/build/Products/Debug-maccatalyst/AppName.app
406
+ ```
407
+
408
+ For a production build:
409
+ ```bash
410
+ ti build -p ios -T macos --deploy-type production
411
+ ```
412
+
413
+ The release `.app` bundle will be at:
414
+ ```
415
+ build/iphone/build/Products/Release-maccatalyst/AppName.app
416
+ ```
417
+
418
+ ### Mac App Store (Mac Catalyst Distribution)
419
+
420
+ ```bash
421
+ ti build -p ios -T dist-macappstore [-R <CERT_NAME>]
422
+ ```
423
+
424
+ Example:
425
+ ```bash
426
+ ti build -p ios -T dist-macappstore -R "Apple Distribution: Your Name (TEAM_ID)"
427
+ ```
428
+
429
+ **Important Notes:**
430
+ - Requires a Mac App Store Distribution Certificate (not iOS Distribution)
431
+ - The build creates an archive for Mac App Store distribution
432
+ - The `.xcarchive` is installed in Xcode's Organizer
433
+ - Uses `Release-maccatalyst` configuration
434
+ - Destination: `generic/platform=macOS`
435
+ - Code signing is set to Manual with identity `-`
436
+
437
+ The target `dist-macappstore` is available in Titanium SDK 13.1.1.GA and later.
438
+
397
439
  ---
398
440
 
399
441
  ## SDK management