@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 +60 -14
- package/package.json +2 -2
- package/skill-packs/superpowers/skills/single-flow-task-execution/SKILL.md +50 -0
- package/skills/admob-roas/SKILL.md +130 -0
- package/skills/orchestrator/SKILL.md +63 -465
- package/skills/symphony-enforcer/SKILL.md +208 -19
- package/skills/trello-sync/SKILL.md +62 -36
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}`))
|
|
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}`))
|
|
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
|
-
|
|
1844
|
-
|
|
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(
|
|
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
|
|
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', '--
|
|
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
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
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.
|
|
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.
|