@leejungkiin/awkit 1.6.6 → 1.7.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 (47) hide show
  1. package/bin/awk.js +186 -8
  2. package/package.json +5 -3
  3. package/schemas/onboarding-screen.schema.json +108 -0
  4. package/scripts/__pycache__/openrouter_image_gen.cpython-311.pyc +0 -0
  5. package/scripts/automation-gate.js +8 -7
  6. package/scripts/cockpit-quota.js +93 -0
  7. package/scripts/exec-rtk.js +50 -0
  8. package/scripts/openrouter_image_gen.py +772 -0
  9. package/scripts/video-analyzer.js +172 -0
  10. package/skills/CATALOG.md +3 -2
  11. package/skills/TRIGGER_INDEX.md +1 -1
  12. package/skills/ai-sprite-maker/SKILL.md +27 -6
  13. package/skills/ai-sprite-maker/scripts/__pycache__/remove_chroma_key.cpython-311.pyc +0 -0
  14. package/skills/ai-sprite-maker/scripts/remove_chroma_key.py +440 -0
  15. package/skills/awf-caveman/SKILL.md +65 -0
  16. package/skills/expo-build-optimizer/SKILL.md +33 -0
  17. package/skills/ios-app-store-audit/SKILL.md +48 -0
  18. package/skills/ios-expert-coder/SKILL.md +45 -0
  19. package/skills/marketing-spec-writer/SKILL.md +51 -0
  20. package/skills/marketing-spec-writer/templates/MARKETING_SPEC.md +53 -0
  21. package/skills/mascot-designer/SKILL.md +66 -0
  22. package/skills/mascot-designer/examples/witny-case-study.md +35 -0
  23. package/skills/orchestrator/SKILL.md +20 -0
  24. package/skills/review/SKILL.md +87 -0
  25. package/skills/short-maker/scripts/google-flow-cli/README.md +227 -115
  26. package/skills/short-maker/scripts/google-flow-cli/gflow/api/client.py +32 -3
  27. package/skills/short-maker/scripts/google-flow-cli/gflow/api/models.py +4 -2
  28. package/skills/short-maker/scripts/google-flow-cli/gflow/cli/main.py +33 -6
  29. package/skills/short-maker/scripts/google-flow-cli/pyproject.toml +1 -1
  30. package/skills/storyboard-to-scene-pack/SKILL.md +102 -0
  31. package/skills/storyboard-to-scene-pack/agents/openai.yaml +4 -0
  32. package/skills/storyboard-to-scene-pack/assets/preview-template/index.html +101 -0
  33. package/skills/storyboard-to-scene-pack/references/continuity-checklist.md +32 -0
  34. package/skills/storyboard-to-scene-pack/references/scene-prompt-template.md +19 -0
  35. package/skills/storyboard-to-scene-pack/references/storyboard-sheet-template.md +14 -0
  36. package/skills/verification-gate/SKILL.md +4 -0
  37. package/templates/help.html +21 -0
  38. package/templates/project-identity/android.json +24 -0
  39. package/templates/project-identity/backend-nestjs.json +24 -0
  40. package/templates/project-identity/expo.json +24 -0
  41. package/templates/project-identity/ios.json +24 -0
  42. package/templates/project-identity/web-nextjs.json +24 -0
  43. package/templates/specs/design-template.md +71 -161
  44. package/templates/specs/requirements-template.md +133 -65
  45. package/workflows/ui/create-spec-architect.md +80 -50
  46. package/workflows/ui/image-gen.md +118 -0
  47. package/skills/code-review/SKILL.md +0 -115
package/bin/awk.js CHANGED
@@ -41,6 +41,8 @@ const { generateClaudeRules, generateClaudeSkills } = require('./claude-generato
41
41
  const { generateCursorRules, generateCursorSkills } = require('./cursor-generators');
42
42
  const { cmdGate } = require('../scripts/automation-gate');
43
43
  const { cmdObsidian } = require('../scripts/obsidian-sync');
44
+ const { cmdQuota } = require('../scripts/cockpit-quota');
45
+ const { cmdVideo } = require('../scripts/video-analyzer');
44
46
 
45
47
  // ─── Platform Definitions ──────────────────────────────────────────────────
46
48
 
@@ -55,6 +57,7 @@ const PLATFORMS = {
55
57
  skills: 'skills',
56
58
  schemas: 'schemas',
57
59
  templates: 'templates',
60
+ scripts: 'scripts',
58
61
  },
59
62
  supportsCustomModes: false,
60
63
  supportsSubagents: false,
@@ -736,6 +739,15 @@ function cmdInstall(args = []) {
736
739
  ok(`${tmplCount} templates installed`);
737
740
  }
738
741
 
742
+ // 8.5 Copy scripts (always overwrite)
743
+ if (plat.dirs.scripts) {
744
+ info('Installing scripts...');
745
+ const scriptsSrc = path.join(AWK_ROOT, 'scripts');
746
+ const scriptsDest = path.join(target, plat.dirs.scripts);
747
+ const scriptsCount = copyDirRecursive(scriptsSrc, scriptsDest);
748
+ ok(`${scriptsCount} scripts installed`);
749
+ }
750
+
739
751
  // 9. Save version
740
752
  fs.writeFileSync(plat.versionFile, AWK_VERSION);
741
753
  ok(`Version ${AWK_VERSION} saved`);
@@ -986,6 +998,18 @@ function cmdDoctor() {
986
998
  warn('schemas/ directory missing'); issues++;
987
999
  }
988
1000
 
1001
+ // 4.5. Check RTK Integration
1002
+ try {
1003
+ const { checkRtk } = require('../scripts/exec-rtk');
1004
+ if (checkRtk()) {
1005
+ ok('RTK (Rust Token Killer) is installed and active');
1006
+ } else {
1007
+ warn('RTK (Rust Token Killer) not found. Run "awkit rtk setup" to optimize token usage.');
1008
+ }
1009
+ } catch (e) {
1010
+ warn(`RTK check failed: ${e.message}`);
1011
+ }
1012
+
989
1013
  // 5. Check version
990
1014
  if (fs.existsSync(TARGETS.versionFile)) {
991
1015
  const v = fs.readFileSync(TARGETS.versionFile, 'utf8').trim();
@@ -2019,6 +2043,10 @@ function cmdHelp() {
2019
2043
  log(` ${C.green}update${C.reset} Pull latest + reinstall`);
2020
2044
  log(` ${C.green}lint${C.reset} Run skill & workflow guards (check length, frontmatter)`);
2021
2045
  log(` ${C.green}doctor${C.reset} Check installation health`);
2046
+ log(` ${C.green}rtk setup${C.reset} Auto-install and configure RTK wrapper`);
2047
+ log(` ${C.green}credentials list${C.reset} List stored API keys`);
2048
+ log(` ${C.green}credentials set${C.reset} <k> <v> Set API key (e.g., gemini_api_key, openrouter_api_key)`);
2049
+ log(` ${C.green}set-openrouter${C.reset} <key> Shorthand for setting OpenRouter API Key`);
2022
2050
  log('');
2023
2051
 
2024
2052
  // Project Init
@@ -2034,6 +2062,13 @@ function cmdHelp() {
2034
2062
  log(`${C.bold}🧹 Maintenance${C.reset}`);
2035
2063
  log(line);
2036
2064
  log(` ${C.green}serve${C.reset} [dir] [-p <port>] Start local HTTP server for assets in CWD`);
2065
+ log(` ${C.green}video${C.reset} <file.mp4> [pmt] Phân tích video/screen record bằng Gemini API`);
2066
+ log(` ${C.gray} --pro${C.reset} Dùng Gemini 3.1 Pro (Chất lượng cao nhất)`);
2067
+ log(` ${C.gray} --flash${C.reset} Dùng Gemini 3 Flash (Nhanh & Tiết kiệm)`);
2068
+ log(` ${C.gray} --debug${C.reset} Dùng prompt tìm lỗi, crash, log`);
2069
+ log(` ${C.gray} --uiux${C.reset} Dùng prompt đánh giá giao diện, animation`);
2070
+ log(` ${C.gray} --clone${C.reset} Dùng prompt bóc băng cấu trúc app đối thủ`);
2071
+ log(` ${C.green}quota${C.reset} Check AI Model Quota from Cockpit extension`);
2037
2072
  log(` ${C.green}browser clean${C.reset} Clean browser recordings`);
2038
2073
  log(` ${C.gray} --days <N>${C.reset} Keep recordings from last N days (default: 7)`);
2039
2074
  log(` ${C.gray} --all${C.reset} Delete all recordings`);
@@ -2349,6 +2384,24 @@ function buildProjectIdentity(projectName, projectType, cwd, date) {
2349
2384
  git: {
2350
2385
  autoCommit: true,
2351
2386
  autoPush: true
2387
+ },
2388
+ obsidian: {
2389
+ enabled: false,
2390
+ path: "",
2391
+ autoSync: false
2392
+ },
2393
+ mcp: {
2394
+ "pixel-mcp": {
2395
+ enabled: false
2396
+ }
2397
+ }
2398
+ },
2399
+ modelPolicy: {
2400
+ mode: "auto",
2401
+ defaultTier: "STANDARD",
2402
+ tierOverrides: {
2403
+ "*.plist|*.json|*.env": "LIGHT",
2404
+ "docs/*": "LIGHT"
2352
2405
  }
2353
2406
  },
2354
2407
  projectStage: 'development',
@@ -2574,16 +2627,51 @@ async function cmdInit(forceFlag = false) {
2574
2627
  git: {
2575
2628
  autoCommit: true,
2576
2629
  autoPush: true
2630
+ },
2631
+ obsidian: {
2632
+ enabled: false,
2633
+ path: "",
2634
+ autoSync: false
2635
+ },
2636
+ mcp: {
2637
+ "pixel-mcp": {
2638
+ enabled: false
2639
+ }
2577
2640
  }
2578
2641
  };
2579
2642
  fs.writeFileSync(identityPath, JSON.stringify(currentIdentity, null, 2) + '\n');
2580
2643
  ok('Added Automation config placeholder to .project-identity');
2581
- } else if (currentIdentity.automation.autoQA === undefined) {
2582
- // Update existing automation block with new QA fields
2583
- currentIdentity.automation.autoQA = true;
2584
- currentIdentity.automation.maxSelfCorrectionLoops = 3;
2585
- fs.writeFileSync(identityPath, JSON.stringify(currentIdentity, null, 2) + '\n');
2586
- ok('Updated Automation config with autoQA defaults in .project-identity');
2644
+ } else {
2645
+ let changed = false;
2646
+ if (currentIdentity.automation.autoQA === undefined) {
2647
+ // Update existing automation block with new QA fields
2648
+ currentIdentity.automation.autoQA = true;
2649
+ currentIdentity.automation.maxSelfCorrectionLoops = 3;
2650
+ changed = true;
2651
+ }
2652
+ if (!currentIdentity.automation.obsidian) {
2653
+ currentIdentity.automation.obsidian = { enabled: false, path: "", autoSync: false };
2654
+ changed = true;
2655
+ }
2656
+ if (!currentIdentity.automation.mcp) {
2657
+ currentIdentity.automation.mcp = { "pixel-mcp": { enabled: false } };
2658
+ changed = true;
2659
+ }
2660
+ if (!currentIdentity.modelPolicy) {
2661
+ currentIdentity.modelPolicy = {
2662
+ mode: "auto",
2663
+ defaultTier: "STANDARD",
2664
+ tierOverrides: {
2665
+ "*.plist|*.json|*.env": "LIGHT",
2666
+ "docs/*": "LIGHT"
2667
+ }
2668
+ };
2669
+ changed = true;
2670
+ }
2671
+ if (changed) {
2672
+ fs.writeFileSync(identityPath, JSON.stringify(currentIdentity, null, 2) + '\n');
2673
+ ok('Updated Automation/ModelPolicy config with defaults in .project-identity');
2674
+ }
2587
2675
  }
2588
2676
  } catch (_) { /* ignore */ }
2589
2677
 
@@ -3042,7 +3130,7 @@ function credentialsHelp() {
3042
3130
  log(` ${C.green}awkit credentials remove${C.reset} <key> Remove a credential`);
3043
3131
  log(` ${C.green}awkit credentials setup${C.reset} Interactive setup wizard`);
3044
3132
  log('');
3045
- log(` ${C.gray}Known keys: gemini_api_key, lucylab_bearer${C.reset}`);
3133
+ log(` ${C.gray}Known keys: gemini_api_key, openrouter_api_key, lucylab_bearer${C.reset}`);
3046
3134
  log(` ${C.gray}Config: ${CREDENTIALS_CONFIG_PATH}${C.reset}`);
3047
3135
  log('');
3048
3136
  }
@@ -3089,6 +3177,20 @@ async function credentialsSetup() {
3089
3177
  dim('Kept existing LucyLab Bearer');
3090
3178
  }
3091
3179
 
3180
+ log('');
3181
+
3182
+ // OpenRouter API Key
3183
+ log(`${C.gray} OpenRouter API Key for Premium Image Generation (GPT-5.4)${C.reset}`);
3184
+ log(`${C.gray} Get your key at: https://openrouter.ai/keys${C.reset}`);
3185
+ const openrouterKey = sanitize(await question(` ${C.yellow}OpenRouter API Key${config.openrouter_api_key ? ` [${config.openrouter_api_key.slice(0, 8)}...]` : ''}: ${C.reset}`));
3186
+ if (openrouterKey) {
3187
+ config.openrouter_api_key = openrouterKey;
3188
+ ok('OpenRouter API Key saved');
3189
+ } else if (config.openrouter_api_key) {
3190
+ dim('Kept existing OpenRouter API Key');
3191
+ }
3192
+
3193
+ log('');
3092
3194
  credentialsSave(config);
3093
3195
  log('');
3094
3196
  ok(`Credentials saved to ${CREDENTIALS_CONFIG_PATH}`);
@@ -3759,6 +3861,65 @@ function cmdGitnexus(args) {
3759
3861
  }
3760
3862
  }
3761
3863
 
3864
+ // ─── RTK Integration ──────────────────────────────────────────────────────────
3865
+
3866
+ async function cmdRtkSetup() {
3867
+ log(`${C.cyan}${C.bold}╔═══════════════════════════════════════════════════════╗${C.reset}`);
3868
+ log(`${C.cyan}${C.bold}║ ⚙️ RTK Auto-Setup & Configuration ║${C.reset}`);
3869
+ log(`${C.cyan}${C.bold}╚═══════════════════════════════════════════════════════╝${C.reset}`);
3870
+
3871
+ log(`\nChecking dependencies...`);
3872
+ try {
3873
+ execSync('which cargo', { stdio: 'ignore' });
3874
+ ok('Cargo is installed.');
3875
+ } catch (_) {
3876
+ err('Cargo/Rust not found. Please install Rust (https://rustup.rs/) and retry.');
3877
+ return;
3878
+ }
3879
+
3880
+ log(`\nInstalling RTK (Rust Token Killer) via Cargo...`);
3881
+ try {
3882
+ execSync('cargo install --git https://github.com/rtk-ai/rtk', { stdio: 'inherit' });
3883
+ ok('RTK successfully installed/updated via Cargo.');
3884
+ } catch (e) {
3885
+ err(`Failed to install RTK: ${e.message}`);
3886
+ return;
3887
+ }
3888
+
3889
+ const zshrcPath = path.join(HOME, '.zshrc');
3890
+ log(`\nConfiguring Shell Aliases in ~/.zshrc...`);
3891
+ if (fs.existsSync(zshrcPath)) {
3892
+ const zshrc = fs.readFileSync(zshrcPath, 'utf8');
3893
+ const aliasBlock = `
3894
+ # RTK Alias for AI Agents (AWKit)
3895
+ alias git="rtk git"
3896
+ alias npm="rtk npm"
3897
+ alias pnpm="rtk pnpm"
3898
+ alias yarn="rtk yarn"
3899
+ alias cargo="rtk cargo"
3900
+ `;
3901
+ if (zshrc.includes('alias git="rtk git"')) {
3902
+ ok('Aliases already present in ~/.zshrc.');
3903
+ } else {
3904
+ fs.appendFileSync(zshrcPath, aliasBlock, 'utf8');
3905
+ ok('Shell aliases successfully added to ~/.zshrc.');
3906
+ log(`\n${C.cyan}Please reload your shell (source ~/.zshrc) to apply shifts.${C.reset}`);
3907
+ }
3908
+ } else {
3909
+ warn('~/.zshrc not found. Skipping alias setup.');
3910
+ }
3911
+ }
3912
+
3913
+ async function cmdRtk(args) {
3914
+ const action = args[0];
3915
+ if (action === 'setup') {
3916
+ await cmdRtkSetup();
3917
+ } else {
3918
+ err(`Unknown rtk command: ${action || ''}`);
3919
+ log(` Available: setup`);
3920
+ }
3921
+ }
3922
+
3762
3923
  // ─── Native HTTP Server ───────────────────────────────────────────────────────
3763
3924
 
3764
3925
  function cmdServe(args) {
@@ -3918,9 +4079,21 @@ const [, , command, ...args] = process.argv;
3918
4079
  case 'creds':
3919
4080
  cmdCredentials(args);
3920
4081
  break;
4082
+ case 'set-openrouter': {
4083
+ const key = args[0];
4084
+ if (!key) {
4085
+ err('Usage: awkit set-openrouter <api_key>');
4086
+ return;
4087
+ }
4088
+ cmdCredentials(['set', 'openrouter_api_key', key]);
4089
+ break;
4090
+ }
3921
4091
  case 'serve':
3922
4092
  cmdServe(args);
3923
4093
  break;
4094
+ case 'rtk':
4095
+ await cmdRtk(args);
4096
+ break;
3924
4097
  case 'gitnexus':
3925
4098
  case 'gn':
3926
4099
  cmdGitnexus(args);
@@ -3928,13 +4101,18 @@ const [, , command, ...args] = process.argv;
3928
4101
  case 'gate':
3929
4102
  cmdGate(args);
3930
4103
  break;
3931
- case 'obsidian':
3932
4104
  case 'obs':
3933
4105
  cmdObsidian(args);
3934
4106
  break;
4107
+ case 'quota':
4108
+ cmdQuota(args);
4109
+ break;
3935
4110
  case 'admin':
3936
4111
  cmdAdmin();
3937
4112
  break;
4113
+ case 'video':
4114
+ await cmdVideo(args);
4115
+ break;
3938
4116
  case 'restart':
3939
4117
  await cmdRestart();
3940
4118
  break;
package/package.json CHANGED
@@ -1,8 +1,9 @@
1
1
  {
2
2
  "name": "@leejungkiin/awkit",
3
- "version": "1.6.6",
3
+ "version": "1.7.0",
4
4
  "description": "Antigravity Workflow Kit v1.6 Unified AI agent orchestration system with Mindful Checkpoints.",
5
5
  "main": "bin/awk.js",
6
+ "private": false,
6
7
  "bin": {
7
8
  "awkit": "bin/awk.js",
8
9
  "ag": "bin/awk.js"
@@ -38,7 +39,8 @@
38
39
  "CHANGELOG.md"
39
40
  ],
40
41
  "dependencies": {
41
- "@leejungkiin/awkit-symphony": "^0.1.0",
42
- "@duytransipher/gitnexus": "latest"
42
+ "@duytransipher/gitnexus": "latest",
43
+ "@google/genai": "^1.50.1",
44
+ "@leejungkiin/awkit-symphony": "^0.1.0"
43
45
  }
44
46
  }
@@ -0,0 +1,108 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "title": "Onboarding Quiz Funnel Configuration",
4
+ "description": "Schema for defining a highly-converting, behaviorally-driven onboarding quiz funnel.",
5
+ "type": "object",
6
+ "required": ["funnelId", "screens"],
7
+ "properties": {
8
+ "funnelId": {
9
+ "type": "string",
10
+ "description": "Unique identifier for this onboarding funnel."
11
+ },
12
+ "defaultMascotId": {
13
+ "type": "string",
14
+ "description": "ID of the default mascot to use if a screen doesn't override it."
15
+ },
16
+ "screens": {
17
+ "type": "array",
18
+ "description": "An ordered array of screens the user will navigate through.",
19
+ "items": {
20
+ "$ref": "#/definitions/Screen"
21
+ }
22
+ }
23
+ },
24
+ "definitions": {
25
+ "Screen": {
26
+ "type": "object",
27
+ "required": ["id", "phase", "type", "title"],
28
+ "properties": {
29
+ "id": {
30
+ "type": "string",
31
+ "description": "Unique identifier for the screen."
32
+ },
33
+ "phase": {
34
+ "type": "string",
35
+ "enum": ["hook", "therapy", "objection", "labor_illusion", "reveal", "paywall"],
36
+ "description": "The psychological phase this screen belongs to."
37
+ },
38
+ "type": {
39
+ "type": "string",
40
+ "enum": ["intro", "single_choice", "multi_choice", "slider", "loading_simulation", "summary", "paywall"],
41
+ "description": "The functional UI type of the screen."
42
+ },
43
+ "title": {
44
+ "type": "string",
45
+ "description": "Main heading or question displayed to the user."
46
+ },
47
+ "subtitle": {
48
+ "type": "string",
49
+ "description": "Secondary text providing context or reassurance."
50
+ },
51
+ "mascotState": {
52
+ "type": "string",
53
+ "enum": ["none", "greeting", "thinking", "encouraging", "empathizing", "analyzing", "presenting", "celebrating", "idle"],
54
+ "description": "The emotional state of the virtual coach to display."
55
+ },
56
+ "progressSpeed": {
57
+ "type": "string",
58
+ "enum": ["fast", "normal", "slow", "jump"],
59
+ "description": "Manipulative progress bar speed for this screen."
60
+ },
61
+ "options": {
62
+ "type": "array",
63
+ "description": "Choices presented to the user (for single_choice or multi_choice types).",
64
+ "items": {
65
+ "$ref": "#/definitions/Option"
66
+ }
67
+ },
68
+ "loadingConfiguration": {
69
+ "type": "object",
70
+ "description": "Configuration specifically for labor_illusion screens.",
71
+ "properties": {
72
+ "durationMs": { "type": "integer" },
73
+ "fakeSteps": {
74
+ "type": "array",
75
+ "items": { "type": "string" }
76
+ }
77
+ }
78
+ }
79
+ }
80
+ },
81
+ "Option": {
82
+ "type": "object",
83
+ "required": ["id", "label"],
84
+ "properties": {
85
+ "id": {
86
+ "type": "string"
87
+ },
88
+ "label": {
89
+ "type": "string",
90
+ "description": "The text displayed on the button."
91
+ },
92
+ "icon": {
93
+ "type": "string",
94
+ "description": "Optional icon identifier for the option."
95
+ },
96
+ "tagsToApply": {
97
+ "type": "array",
98
+ "description": "List of tags to assign to the user if they select this option.",
99
+ "items": { "type": "string" }
100
+ },
101
+ "reinforcementMessage": {
102
+ "type": "string",
103
+ "description": "An intermittent reinforcement message to show after selecting this option (e.g., '70% of our users feel the same way.')."
104
+ }
105
+ }
106
+ }
107
+ }
108
+ }
@@ -18,6 +18,7 @@
18
18
  const fs = require('fs');
19
19
  const path = require('path');
20
20
  const { execSync } = require('child_process');
21
+ const { execRtkSync } = require('./exec-rtk');
21
22
 
22
23
  // Lazy-load obsidian-sync to avoid circular dependencies
23
24
  let _autoSyncObsidian = null;
@@ -174,8 +175,8 @@ function execGitCommit(message) {
174
175
  }
175
176
 
176
177
  try {
177
- execSync('git add -A', { stdio: 'inherit' });
178
- execSync(`git commit -m "${message.replace(/"/g, '\\"')}"`, { stdio: 'inherit' });
178
+ execRtkSync('git add -A', { stdio: 'inherit' });
179
+ execRtkSync(`git commit -m "${message.replace(/"/g, '\\"')}"`, { stdio: 'inherit' });
179
180
  ok(`Committed: ${message}`);
180
181
  return true;
181
182
  } catch (e) {
@@ -198,13 +199,13 @@ function execGitPush() {
198
199
  }
199
200
 
200
201
  try {
201
- execSync('git push', { stdio: 'inherit' });
202
+ execRtkSync('git push', { stdio: 'inherit' });
202
203
  ok('Pushed successfully.');
203
204
  return true;
204
205
  } catch (e) {
205
206
  warn('Push failed. Retrying with git pull --rebase...');
206
207
  try {
207
- execSync('git pull --rebase && git push', { stdio: 'inherit' });
208
+ execRtkSync('git pull --rebase && git push', { stdio: 'inherit' });
208
209
  ok('Pushed successfully after rebase.');
209
210
  return true;
210
211
  } catch (e2) {
@@ -231,7 +232,7 @@ function execGitAuto(message) {
231
232
  if (tgGate.allowed) {
232
233
  info('Triggering Telegram notification...');
233
234
  try {
234
- execSync(`awkit tg send "✅ Pushed: ${message.replace(/"/g, '\\"')}"`, { stdio: 'inherit' });
235
+ execRtkSync(`awkit tg send "✅ Pushed: ${message.replace(/"/g, '\\"')}"`, { stdio: 'inherit' });
235
236
  } catch (_) {
236
237
  dim('Telegram notification skipped (not configured or failed).');
237
238
  }
@@ -255,7 +256,7 @@ function execTrelloAction(action, args) {
255
256
 
256
257
  try {
257
258
  const cmd = `awkit trello ${action} ${args.map(a => `"${a}"`).join(' ')}`;
258
- execSync(cmd, { stdio: 'inherit' });
259
+ execRtkSync(cmd, { stdio: 'inherit' });
259
260
  // Trigger Obsidian sync after successful trello action
260
261
  getAutoSyncObsidian()();
261
262
  return true;
@@ -277,7 +278,7 @@ function execTelegramSend(args) {
277
278
 
278
279
  try {
279
280
  const cmd = `awkit tg send ${args.map(a => `"${a}"`).join(' ')}`;
280
- execSync(cmd, { stdio: 'inherit' });
281
+ execRtkSync(cmd, { stdio: 'inherit' });
281
282
  return true;
282
283
  } catch (e) {
283
284
  err(`Telegram send failed: ${e.message}`);
@@ -0,0 +1,93 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const os = require('os');
4
+
5
+ const C = {
6
+ reset: '\x1b[0m',
7
+ green: '\x1b[32m',
8
+ yellow: '\x1b[33m',
9
+ red: '\x1b[31m',
10
+ cyan: '\x1b[36m',
11
+ bold: '\x1b[1m',
12
+ gray: '\x1b[90m',
13
+ };
14
+
15
+ function cmdQuota(args) {
16
+ const quotaDir = path.join(os.homedir(), '.antigravity_cockpit', 'cache', 'quota_api_v1_plugin', 'authorized');
17
+
18
+ if (!fs.existsSync(quotaDir)) {
19
+ console.log(`${C.yellow}⚠️ Cockpit quota cache not found at ${quotaDir}${C.reset}`);
20
+ return;
21
+ }
22
+
23
+ const files = fs.readdirSync(quotaDir).filter(f => f.endsWith('.json'));
24
+ if (files.length === 0) {
25
+ console.log(`${C.yellow}⚠️ No authorized quota files found. Make sure Cockpit extension is logged in.${C.reset}`);
26
+ return;
27
+ }
28
+
29
+ let allAccounts = [];
30
+
31
+ for (const file of files) {
32
+ try {
33
+ const data = JSON.parse(fs.readFileSync(path.join(quotaDir, file), 'utf8'));
34
+ if (data && data.email && data.payload && data.payload.models) {
35
+ allAccounts.push(data);
36
+ }
37
+ } catch (e) {
38
+ // ignore parse error
39
+ }
40
+ }
41
+
42
+ if (allAccounts.length === 0) {
43
+ console.log(`${C.yellow}⚠️ Quota files unreadable.${C.reset}`);
44
+ return;
45
+ }
46
+
47
+ // Sort by most recently updated
48
+ allAccounts.sort((a, b) => (b.updatedAt || 0) - (a.updatedAt || 0));
49
+
50
+ console.log(`${C.cyan}${C.bold}🚀 AI Model Quota Status (Antigravity Cockpit)${C.reset}\n`);
51
+
52
+ for (const account of allAccounts) {
53
+ const timeAgoMs = Date.now() - (account.updatedAt || Date.now());
54
+ const timeAgoMins = Math.round(Math.max(0, timeAgoMs) / 60000);
55
+ console.log(`${C.bold}📧 Account: ${account.email}${C.reset} ${C.gray}(Updated ${timeAgoMins} mins ago)${C.reset}`);
56
+
57
+ const models = Object.values(account.payload.models).filter(m => m.quotaInfo && m.quotaInfo.remainingFraction !== undefined);
58
+
59
+ if (models.length === 0) {
60
+ console.log(` ${C.gray}No models found with quota info.${C.reset}\n`);
61
+ continue;
62
+ }
63
+
64
+ // Sort by displayName length to pad
65
+ const maxLen = Math.max(...models.map(m => (m.displayName || m.model).length));
66
+
67
+ // Sort models by displayName for consistent viewing
68
+ models.sort((a, b) => (a.displayName || a.model).localeCompare(b.displayName || b.model));
69
+
70
+ for (const m of models) {
71
+ const name = (m.displayName || m.model).padEnd(maxLen);
72
+ const fraction = m.quotaInfo.remainingFraction;
73
+ const pct = (fraction * 100).toFixed(2).padStart(6, ' ') + '%';
74
+
75
+ let color = C.green;
76
+ if (fraction <= 0.2) color = C.red;
77
+ else if (fraction < 0.5) color = C.yellow;
78
+
79
+ let resetTimeStr = '';
80
+ if (m.quotaInfo.resetTime) {
81
+ const resetDate = new Date(m.quotaInfo.resetTime);
82
+ if (resetDate.getTime() > Date.now()) {
83
+ resetTimeStr = ` (Resets: ${resetDate.toLocaleTimeString()})`;
84
+ }
85
+ }
86
+
87
+ console.log(` ${name} : ${color}${pct}${C.reset}${C.gray}${resetTimeStr}${C.reset}`);
88
+ }
89
+ console.log('');
90
+ }
91
+ }
92
+
93
+ module.exports = { cmdQuota };
@@ -0,0 +1,50 @@
1
+ const { execSync } = require('child_process');
2
+
3
+ let hasRtkCache = null;
4
+
5
+ /**
6
+ * Check if rtk binary is available in the current environment PATH.
7
+ * @returns {boolean}
8
+ */
9
+ function checkRtk() {
10
+ if (hasRtkCache !== null) return hasRtkCache;
11
+ try {
12
+ execSync('which rtk', { stdio: 'ignore' });
13
+ hasRtkCache = true;
14
+ } catch (_) {
15
+ hasRtkCache = false;
16
+ }
17
+ return hasRtkCache;
18
+ }
19
+
20
+ /**
21
+ * Execute command with rtk prefix if available and eligible.
22
+ * @param {string} command
23
+ * @param {object} options
24
+ * @returns {Buffer|string}
25
+ */
26
+ function execRtkSync(command, options = {}) {
27
+ if (!checkRtk()) {
28
+ return execSync(command, options);
29
+ }
30
+
31
+ const trimmed = command.trim();
32
+ if (trimmed.startsWith('rtk ')) {
33
+ return execSync(command, options);
34
+ }
35
+
36
+ // Only compress developer commands known to be verbose
37
+ const eligiblePrefixes = ['git ', 'npm ', 'cargo ', 'yarn ', 'pnpm ', 'grep ', 'find ', 'awkit trello '];
38
+ const isEligible = eligiblePrefixes.some(prefix => trimmed.startsWith(prefix));
39
+
40
+ if (isEligible) {
41
+ return execSync(`rtk ${command}`, options);
42
+ }
43
+
44
+ return execSync(command, options);
45
+ }
46
+
47
+ module.exports = {
48
+ execRtkSync,
49
+ checkRtk
50
+ };