@leejungkiin/awkit 1.3.4 → 1.3.8

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/bin/awk.js CHANGED
@@ -27,6 +27,7 @@ const fs = require('fs');
27
27
  const path = require('path');
28
28
  const https = require('https');
29
29
  const { execSync, spawnSync } = require('child_process');
30
+ const os = require('os');
30
31
 
31
32
  const packageJson = require(path.join(__dirname, '..', 'package.json'));
32
33
  const AWK_VERSION = packageJson.version;
@@ -1817,9 +1818,11 @@ async function cmdInit(forceFlag = false) {
1817
1818
  });
1818
1819
 
1819
1820
  const question = (query) => new Promise(resolve => rl.question(query, resolve));
1821
+ // Sanitize: strip ALL whitespace (spaces, tabs, newlines) from pasted input
1822
+ const sanitize = (s) => s.replace(/\s+/g, '');
1820
1823
 
1821
1824
  try {
1822
- const apiKey = (await question(` ${C.yellow}Enter Trello API Key: ${C.reset}`)).trim();
1825
+ const apiKey = sanitize(await question(` ${C.yellow}Enter Trello API Key: ${C.reset}`));
1823
1826
 
1824
1827
  if (apiKey) {
1825
1828
  const tokenUrl = `https://trello.com/1/authorize?expiration=never&scope=read,write&response_type=token&key=${apiKey}&name=AWKit`;
@@ -1829,7 +1832,7 @@ async function cmdInit(forceFlag = false) {
1829
1832
  log('');
1830
1833
  }
1831
1834
 
1832
- const apiToken = (await question(` ${C.yellow}Enter Trello API Token: ${C.reset}`)).trim();
1835
+ const apiToken = sanitize(await question(` ${C.yellow}Enter Trello API Token: ${C.reset}`));
1833
1836
 
1834
1837
  if (apiKey && apiToken) {
1835
1838
  let profilePath = path.join(os.homedir(), '.zshrc');
@@ -1840,13 +1843,17 @@ async function cmdInit(forceFlag = false) {
1840
1843
  const exportLines = `\n# Trello API Credentials for AWKit\nexport TRELLO_KEY="${apiKey}"\nexport TRELLO_TOKEN="${apiToken}"\n`;
1841
1844
  fs.appendFileSync(profilePath, exportLines);
1842
1845
 
1843
- ok(`Credentials saved to ${profilePath}`);
1844
- log(` ${C.cyan}👉 Please run 'source ~/${path.basename(profilePath)}' or restart your terminal to apply them.${C.reset}`);
1846
+ // Also inject into current process so immediate awkit trello calls work
1847
+ process.env.TRELLO_KEY = apiKey;
1848
+ process.env.TRELLO_TOKEN = apiToken;
1849
+
1850
+ ok(`Credentials saved to ${path.basename(profilePath)} ✅`);
1851
+ log(` ${C.green}👉 Trello is ready! Credentials are active for this session and all future terminals.${C.reset}`);
1845
1852
  } else {
1846
1853
  warn('Setup skipped. Automated Trello sync will be disabled.');
1847
1854
  }
1848
1855
  } catch (e) {
1849
- warn('Failed to setup Trello interactively.');
1856
+ warn(`Failed to setup Trello: ${e.message}`);
1850
1857
  } finally {
1851
1858
  rl.close();
1852
1859
  }
@@ -2194,12 +2201,12 @@ function cmdTrello(args) {
2194
2201
  const subCmd = args[0];
2195
2202
  const text = args.slice(1).join(' ');
2196
2203
 
2197
- if (!subCmd || subCmd === 'help') {
2204
+ if (!subCmd || subCmd === 'help' || subCmd === '--help' || subCmd === '-h') {
2198
2205
  trelloHelp();
2199
2206
  return;
2200
2207
  }
2201
2208
 
2202
- if (!text && subCmd !== 'help') {
2209
+ if (!text) {
2203
2210
  err(`Missing argument for 'trello ${subCmd}'. Usage: awkit trello ${subCmd} <text>`);
2204
2211
  return;
2205
2212
  }
@@ -2207,7 +2214,7 @@ function cmdTrello(args) {
2207
2214
  switch (subCmd) {
2208
2215
  case 'desc':
2209
2216
  info(`Updating card description...`);
2210
- trelloExec(['card:update', '--desc', text]);
2217
+ trelloExec(['card:update', '--description', text]);
2211
2218
  break;
2212
2219
 
2213
2220
  case 'comment':
@@ -2216,12 +2223,51 @@ function cmdTrello(args) {
2216
2223
  break;
2217
2224
 
2218
2225
  case 'item':
2219
- // trello-cli doesn't support adding checklist items natively.
2220
- // We fall back to marking an item incomplete (which implicitly creates it in some versions)
2221
- // or inform the user to use the REST API.
2222
- info(`Adding checklist item: ${text}`);
2223
- warn('Note: trello-cli may not support adding items. Use REST API if this fails.');
2224
- trelloExec(['card:check-item', '--item', text, '--state', 'incomplete']);
2226
+ info(`Adding checklist item via REST API: ${text}`);
2227
+ const credItem = trelloLoadCredentials();
2228
+ const cfgItem = trelloLoadProjectConfig();
2229
+ if (!credItem || !cfgItem) {
2230
+ err("Credentials or config missing for REST API fallback.");
2231
+ break;
2232
+ }
2233
+
2234
+ // 1. Get checklists
2235
+ const clRes = spawnSync('npx', ['--yes', 'trello-cli', 'card:checklists', '--board', cfgItem.board, '--list', cfgItem.list, '--card', cfgItem.card, '--format', 'json'], { env: { ...process.env, TRELLO_KEY: credItem.api_key, TRELLO_TOKEN: credItem.api_token }, encoding: 'utf-8' });
2236
+
2237
+ if (clRes.status !== 0) {
2238
+ err(`Failed to get checklists: ${clRes.stderr || clRes.stdout}`);
2239
+ break;
2240
+ }
2241
+
2242
+ try {
2243
+ // Sometime trello-cli outputs to stdout along with some other logs.
2244
+ // We extract the JSON array part.
2245
+ const outText = clRes.stdout;
2246
+ const jsonStr = outText.substring(outText.indexOf('['));
2247
+ const checklists = JSON.parse(jsonStr);
2248
+
2249
+ if (!checklists || checklists.length === 0) {
2250
+ err("No checklists found on card. Create one first using 'awkit trello checklist <name>'.");
2251
+ break;
2252
+ }
2253
+
2254
+ // Add to the LAST checklist (most recently appended usually)
2255
+ const targetChecklist = checklists[checklists.length - 1];
2256
+ const checklistId = targetChecklist.id;
2257
+
2258
+ // 2. Add item via curl
2259
+ const url = `https://api.trello.com/1/checklists/${checklistId}/checkItems?name=${encodeURIComponent(text)}&key=${credItem.api_key}&token=${credItem.api_token}`;
2260
+ const addRes = spawnSync('curl', ['-s', '-X', 'POST', url], { encoding: 'utf-8' });
2261
+
2262
+ const responseJson = JSON.parse(addRes.stdout);
2263
+ if (responseJson.id) {
2264
+ ok(`Item added successfully to checklist "${targetChecklist.name}".`);
2265
+ } else {
2266
+ err(`Failed to add item via REST API: ${addRes.stdout}`);
2267
+ }
2268
+ } catch (e) {
2269
+ err(`Failed to parse checklists or execute curl: ${e.message}`);
2270
+ }
2225
2271
  break;
2226
2272
 
2227
2273
  case 'complete':
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@leejungkiin/awkit",
3
- "version": "1.3.4",
3
+ "version": "1.3.8",
4
4
  "description": "Antigravity Workflow Kit. Unified AI agent orchestration system.",
5
5
  "main": "bin/awk.js",
6
6
  "bin": {
@@ -40,4 +40,4 @@
40
40
  "@leejungkiin/awkit-symphony": "^0.1.0",
41
41
  "@leejungkiin/gitnexus": "file:../gitnexus/gitnexus"
42
42
  }
43
- }
43
+ }
@@ -110,6 +110,56 @@ digraph process {
110
110
  }
111
111
  ```
112
112
 
113
+ ## UI-First Task Ordering (Gate 4 Three-Phase — v12.3)
114
+
115
+ When a task set includes UI components (COMPLEX or MODERATE), tasks MUST be ordered in three phases:
116
+
117
+ ### Phase A: Infrastructure Tasks
118
+ ```
119
+ Priority: Execute FIRST
120
+ Examples:
121
+ - Add dependencies (Gradle, SPM, CocoaPods)
122
+ - Create project structure (packages, modules, DI)
123
+ - Set up navigation skeleton (NavGraph, Router)
124
+ - Configure build variants, signing
125
+ Gate: App MUST build successfully → proceed
126
+ ```
127
+
128
+ ### Phase B: UI Shell Tasks (Mock Data)
129
+ ```
130
+ Priority: Execute SECOND, BEFORE any logic tasks
131
+ Examples:
132
+ - Build all screen layouts with static/mock data
133
+ - Implement navigation between screens
134
+ - Add animations, transitions, loading/empty/error states
135
+ - Wire up UI components (no real API/DB calls)
136
+ Gate: 🧪 USER TEST CHECKPOINT — user must test UI on device
137
+ → Present test guidance (see symphony-enforcer TP1.7)
138
+ → User confirms UI OK → proceed to Phase C
139
+ → User reports issue → fix → re-checkpoint
140
+ ```
141
+
142
+ ### Phase C: Logic Tasks (Per Feature)
143
+ ```
144
+ Priority: Execute LAST, after UI is confirmed
145
+ Examples:
146
+ - Replace mock data with real API/DB calls
147
+ - Implement business logic, validation
148
+ - Add error handling, retry, offline support
149
+ - Wire up hardware features (camera, GPS, sensors)
150
+ Gate: 🧪 USER TEST CHECKPOINT per feature (batch small ones)
151
+ → Especially important for hardware-dependent features
152
+ ```
153
+
154
+ ### Task Sorting Rule
155
+ ```
156
+ When creating task list from implementation plan:
157
+ 1. Tag each task: [INFRA] [UI] [LOGIC]
158
+ 2. Sort: INFRA first → UI second → LOGIC last
159
+ 3. Within each phase: respect dependency ordering
160
+ 4. Between phases: MANDATORY checkpoint where indicated
161
+ ```
162
+
113
163
  ## Task Decomposition
114
164
 
115
165
  When facing multiple problems (e.g., 5 test failures across 3 files):
@@ -0,0 +1,130 @@
1
+ ---
2
+ name: admob-roas
3
+ description: When the user wants to implement AdMob ROAS tracking, track ad impressions to Firebase, or optimize in-app ad revenue events in Native Android (Kotlin) or iOS (Swift). Also use when the user mentions "tROAS", "ad_impression", "OnPaidEventListener", "paidEventHandler", or "admob ROAS event".
4
+ metadata:
5
+ version: 1.0.0
6
+ ---
7
+
8
+ # ROAS Event Tracking cho AdMob (Native Android & iOS)
9
+
10
+ You are an expert in mobile app analytics and AdMob monetization. Your goal is to help the user implement ROAS event tracking (`ad_impression`) for AdMob in Native Android (Kotlin) and iOS (Swift).
11
+
12
+ > **🎯 Mục tiêu**: Đảm bảo Firebase Analytics nhận được sự kiện `ad_impression` kèm theo giá trị doanh thu (`value`, `currency`) mỗi khi quảng cáo hiển thị. Điều này **bắt buộc** để Google Ads / Facebook Ads có thể chạy tROAS (Target Return On Ad Spend) campaign.
13
+
14
+ ---
15
+
16
+ ## 1. Yêu Cầu Cốt Lõi (Requirements)
17
+
18
+ Khi AI Agent được giao nhiệm vụ tích hợp quảng cáo (AdMob) vào dự án Native Android (Kotlin) hoặc iOS (Swift), **LUÔN** phải đảm bảo:
19
+ - SDK `Firebase Analytics` đã được cài đặt và khởi tạo.
20
+ - Bắt sự kiện **OnPaidEventListener** (Android) hoặc **paidEventHandler** (iOS) trên TẤT CẢ các loại quảng cáo: Banner, Interstitial, Rewarded, AppOpen, Native.
21
+ - Convert chuẩn xác giá trị vi mô (`valueMicros` / `NSDecimalNumber`) ra giá trị thập phân thực.
22
+ - Bắn event `ad_impression` về Firebase.
23
+
24
+ ---
25
+
26
+ ## 2. Standard Pattern: Android (Kotlin)
27
+
28
+ ### Bước 1: Tạo Helper/Tracker Class
29
+
30
+ Tạo file `RoasTracker.kt` ở package data/analytics:
31
+
32
+ ```kotlin
33
+ import android.os.Bundle
34
+ import com.google.android.gms.ads.AdValue
35
+ import com.google.android.gms.ads.AdapterResponseInfo
36
+ import com.google.firebase.analytics.FirebaseAnalytics
37
+
38
+ object RoasTracker {
39
+ fun trackAdMobImpression(
40
+ firebaseAnalytics: FirebaseAnalytics,
41
+ adValue: AdValue,
42
+ responseInfo: AdapterResponseInfo?,
43
+ adFormat: String
44
+ ) {
45
+ val valueMicros = adValue.valueMicros
46
+ val currencyCode = adValue.currencyCode
47
+ val valueDouble = valueMicros / 1_000_000.0 // Convert sang dạng float/double
48
+
49
+ val bundle = Bundle().apply {
50
+ putString(FirebaseAnalytics.Param.AD_PLATFORM, "admob")
51
+ putString(FirebaseAnalytics.Param.AD_SOURCE, responseInfo?.adSourceName ?: "AdMob")
52
+ putString(FirebaseAnalytics.Param.AD_FORMAT, adFormat)
53
+ putDouble(FirebaseAnalytics.Param.VALUE, valueDouble)
54
+ putString(FirebaseAnalytics.Param.CURRENCY, currencyCode)
55
+ }
56
+
57
+ firebaseAnalytics.logEvent(FirebaseAnalytics.Event.AD_IMPRESSION, bundle)
58
+ }
59
+ }
60
+ ```
61
+
62
+ ### Bước 2: Tích hợp vào Ad Lifecycle
63
+
64
+ **Ví dụ với AppOpenAd / InterstitialAd:**
65
+ ```kotlin
66
+ interstitialAd?.onPaidEventListener = OnPaidEventListener { adValue ->
67
+ RoasTracker.trackAdMobImpression(
68
+ firebaseAnalytics = FirebaseAnalytics.getInstance(context),
69
+ adValue = adValue,
70
+ responseInfo = interstitialAd?.responseInfo?.loadedAdapterResponseInfo,
71
+ adFormat = "interstitial"
72
+ )
73
+ }
74
+ ```
75
+
76
+ ---
77
+
78
+ ## 3. Standard Pattern: iOS (Swift)
79
+
80
+ ### Bước 1: Tạo Helper/Tracker Struct
81
+
82
+ Tạo file `RoasTracker.swift`:
83
+
84
+ ```swift
85
+ import Foundation
86
+ import FirebaseAnalytics
87
+ import GoogleMobileAds
88
+
89
+ struct RoasTracker {
90
+ static func trackAdMobImpression(
91
+ adValue: GADAdValue,
92
+ responseInfo: GADAdNetworkResponseInfo?,
93
+ adFormat: String
94
+ ) {
95
+ let value = adValue.value.doubleValue
96
+ let currency = adValue.currencyCode
97
+
98
+ Analytics.logEvent(AnalyticsEventAdImpression, parameters: [
99
+ AnalyticsParameterAdPlatform: "admob",
100
+ AnalyticsParameterAdSource: responseInfo?.adNetworkClassName ?? "AdMob",
101
+ AnalyticsParameterAdFormat: adFormat,
102
+ AnalyticsParameterValue: value,
103
+ AnalyticsParameterCurrency: currency
104
+ ])
105
+ }
106
+ }
107
+ ```
108
+
109
+ ### Bước 2: Tích hợp vào Ad Lifecycle
110
+
111
+ **Ví dụ với GADRewardedAd:**
112
+ ```swift
113
+ rewardedAd?.paidEventHandler = { [weak rewardedAd] adValue in
114
+ guard let ad = rewardedAd else { return }
115
+ RoasTracker.trackAdMobImpression(
116
+ adValue: adValue,
117
+ responseInfo: ad.responseInfo?.loadedAdNetworkResponseInfo,
118
+ adFormat: "rewarded"
119
+ )
120
+ }
121
+ ```
122
+
123
+ ---
124
+
125
+ ## 4. Kiểm tra Validation
126
+
127
+ Một luồng ROAS hợp lệ phải có đủ các yếu tố sau:
128
+ 1. Bạn phải đăng ký `OnPaidEventListener` NGAY SAU KHI Ad báo quá trình load thành công (trong `onAdLoaded`).
129
+ 2. Log output phải hiển thị event `ad_impression` trong Logcat (hoặc Xcode console) qua chế độ `-enable_core_ads` hoặc Firebase Verbose logging `adb shell setprop log.tag.FA VERBOSE`.
130
+ 3. Kiểm tra các event onboarding `tutorial_begin` và `tutorial_complete` để đảm bảo luồng cài đặt và sử dụng ứng dụng được tracking liên kết.