@leejungkiin/awkit 1.6.6 → 1.6.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 +111 -8
- package/package.json +5 -3
- package/schemas/onboarding-screen.schema.json +108 -0
- package/scripts/__pycache__/openrouter_image_gen.cpython-311.pyc +0 -0
- package/scripts/cockpit-quota.js +93 -0
- package/scripts/openrouter_image_gen.py +772 -0
- package/scripts/video-analyzer.js +172 -0
- package/skills/CATALOG.md +2 -1
- package/skills/ai-sprite-maker/SKILL.md +27 -6
- package/skills/ai-sprite-maker/scripts/__pycache__/remove_chroma_key.cpython-311.pyc +0 -0
- package/skills/ai-sprite-maker/scripts/remove_chroma_key.py +440 -0
- package/skills/awf-caveman/SKILL.md +65 -0
- package/skills/expo-build-optimizer/SKILL.md +33 -0
- package/skills/ios-app-store-audit/SKILL.md +48 -0
- package/skills/ios-expert-coder/SKILL.md +45 -0
- package/skills/mascot-designer/SKILL.md +66 -0
- package/skills/mascot-designer/examples/witny-case-study.md +35 -0
- package/skills/orchestrator/SKILL.md +20 -0
- package/skills/short-maker/scripts/google-flow-cli/README.md +227 -115
- package/skills/short-maker/scripts/google-flow-cli/gflow/api/client.py +32 -3
- package/skills/short-maker/scripts/google-flow-cli/gflow/api/models.py +4 -2
- package/skills/short-maker/scripts/google-flow-cli/gflow/cli/main.py +33 -6
- package/skills/short-maker/scripts/google-flow-cli/pyproject.toml +1 -1
- package/skills/verification-gate/SKILL.md +4 -0
- package/templates/help.html +21 -0
- package/templates/project-identity/android.json +24 -0
- package/templates/project-identity/backend-nestjs.json +24 -0
- package/templates/project-identity/expo.json +24 -0
- package/templates/project-identity/ios.json +24 -0
- package/templates/project-identity/web-nextjs.json +24 -0
- package/templates/specs/design-template.md +71 -161
- package/templates/specs/requirements-template.md +133 -65
- package/workflows/ui/create-spec-architect.md +80 -50
- package/workflows/ui/image-gen.md +118 -0
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`);
|
|
@@ -2019,6 +2031,9 @@ function cmdHelp() {
|
|
|
2019
2031
|
log(` ${C.green}update${C.reset} Pull latest + reinstall`);
|
|
2020
2032
|
log(` ${C.green}lint${C.reset} Run skill & workflow guards (check length, frontmatter)`);
|
|
2021
2033
|
log(` ${C.green}doctor${C.reset} Check installation health`);
|
|
2034
|
+
log(` ${C.green}credentials list${C.reset} List stored API keys`);
|
|
2035
|
+
log(` ${C.green}credentials set${C.reset} <k> <v> Set API key (e.g., gemini_api_key, openrouter_api_key)`);
|
|
2036
|
+
log(` ${C.green}set-openrouter${C.reset} <key> Shorthand for setting OpenRouter API Key`);
|
|
2022
2037
|
log('');
|
|
2023
2038
|
|
|
2024
2039
|
// Project Init
|
|
@@ -2034,6 +2049,13 @@ function cmdHelp() {
|
|
|
2034
2049
|
log(`${C.bold}🧹 Maintenance${C.reset}`);
|
|
2035
2050
|
log(line);
|
|
2036
2051
|
log(` ${C.green}serve${C.reset} [dir] [-p <port>] Start local HTTP server for assets in CWD`);
|
|
2052
|
+
log(` ${C.green}video${C.reset} <file.mp4> [pmt] Phân tích video/screen record bằng Gemini API`);
|
|
2053
|
+
log(` ${C.gray} --pro${C.reset} Dùng Gemini 3.1 Pro (Chất lượng cao nhất)`);
|
|
2054
|
+
log(` ${C.gray} --flash${C.reset} Dùng Gemini 3 Flash (Nhanh & Tiết kiệm)`);
|
|
2055
|
+
log(` ${C.gray} --debug${C.reset} Dùng prompt tìm lỗi, crash, log`);
|
|
2056
|
+
log(` ${C.gray} --uiux${C.reset} Dùng prompt đánh giá giao diện, animation`);
|
|
2057
|
+
log(` ${C.gray} --clone${C.reset} Dùng prompt bóc băng cấu trúc app đối thủ`);
|
|
2058
|
+
log(` ${C.green}quota${C.reset} Check AI Model Quota from Cockpit extension`);
|
|
2037
2059
|
log(` ${C.green}browser clean${C.reset} Clean browser recordings`);
|
|
2038
2060
|
log(` ${C.gray} --days <N>${C.reset} Keep recordings from last N days (default: 7)`);
|
|
2039
2061
|
log(` ${C.gray} --all${C.reset} Delete all recordings`);
|
|
@@ -2349,6 +2371,24 @@ function buildProjectIdentity(projectName, projectType, cwd, date) {
|
|
|
2349
2371
|
git: {
|
|
2350
2372
|
autoCommit: true,
|
|
2351
2373
|
autoPush: true
|
|
2374
|
+
},
|
|
2375
|
+
obsidian: {
|
|
2376
|
+
enabled: false,
|
|
2377
|
+
path: "",
|
|
2378
|
+
autoSync: false
|
|
2379
|
+
},
|
|
2380
|
+
mcp: {
|
|
2381
|
+
"pixel-mcp": {
|
|
2382
|
+
enabled: false
|
|
2383
|
+
}
|
|
2384
|
+
}
|
|
2385
|
+
},
|
|
2386
|
+
modelPolicy: {
|
|
2387
|
+
mode: "auto",
|
|
2388
|
+
defaultTier: "STANDARD",
|
|
2389
|
+
tierOverrides: {
|
|
2390
|
+
"*.plist|*.json|*.env": "LIGHT",
|
|
2391
|
+
"docs/*": "LIGHT"
|
|
2352
2392
|
}
|
|
2353
2393
|
},
|
|
2354
2394
|
projectStage: 'development',
|
|
@@ -2574,16 +2614,51 @@ async function cmdInit(forceFlag = false) {
|
|
|
2574
2614
|
git: {
|
|
2575
2615
|
autoCommit: true,
|
|
2576
2616
|
autoPush: true
|
|
2617
|
+
},
|
|
2618
|
+
obsidian: {
|
|
2619
|
+
enabled: false,
|
|
2620
|
+
path: "",
|
|
2621
|
+
autoSync: false
|
|
2622
|
+
},
|
|
2623
|
+
mcp: {
|
|
2624
|
+
"pixel-mcp": {
|
|
2625
|
+
enabled: false
|
|
2626
|
+
}
|
|
2577
2627
|
}
|
|
2578
2628
|
};
|
|
2579
2629
|
fs.writeFileSync(identityPath, JSON.stringify(currentIdentity, null, 2) + '\n');
|
|
2580
2630
|
ok('Added Automation config placeholder to .project-identity');
|
|
2581
|
-
} else
|
|
2582
|
-
|
|
2583
|
-
currentIdentity.automation.autoQA
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2631
|
+
} else {
|
|
2632
|
+
let changed = false;
|
|
2633
|
+
if (currentIdentity.automation.autoQA === undefined) {
|
|
2634
|
+
// Update existing automation block with new QA fields
|
|
2635
|
+
currentIdentity.automation.autoQA = true;
|
|
2636
|
+
currentIdentity.automation.maxSelfCorrectionLoops = 3;
|
|
2637
|
+
changed = true;
|
|
2638
|
+
}
|
|
2639
|
+
if (!currentIdentity.automation.obsidian) {
|
|
2640
|
+
currentIdentity.automation.obsidian = { enabled: false, path: "", autoSync: false };
|
|
2641
|
+
changed = true;
|
|
2642
|
+
}
|
|
2643
|
+
if (!currentIdentity.automation.mcp) {
|
|
2644
|
+
currentIdentity.automation.mcp = { "pixel-mcp": { enabled: false } };
|
|
2645
|
+
changed = true;
|
|
2646
|
+
}
|
|
2647
|
+
if (!currentIdentity.modelPolicy) {
|
|
2648
|
+
currentIdentity.modelPolicy = {
|
|
2649
|
+
mode: "auto",
|
|
2650
|
+
defaultTier: "STANDARD",
|
|
2651
|
+
tierOverrides: {
|
|
2652
|
+
"*.plist|*.json|*.env": "LIGHT",
|
|
2653
|
+
"docs/*": "LIGHT"
|
|
2654
|
+
}
|
|
2655
|
+
};
|
|
2656
|
+
changed = true;
|
|
2657
|
+
}
|
|
2658
|
+
if (changed) {
|
|
2659
|
+
fs.writeFileSync(identityPath, JSON.stringify(currentIdentity, null, 2) + '\n');
|
|
2660
|
+
ok('Updated Automation/ModelPolicy config with defaults in .project-identity');
|
|
2661
|
+
}
|
|
2587
2662
|
}
|
|
2588
2663
|
} catch (_) { /* ignore */ }
|
|
2589
2664
|
|
|
@@ -3042,7 +3117,7 @@ function credentialsHelp() {
|
|
|
3042
3117
|
log(` ${C.green}awkit credentials remove${C.reset} <key> Remove a credential`);
|
|
3043
3118
|
log(` ${C.green}awkit credentials setup${C.reset} Interactive setup wizard`);
|
|
3044
3119
|
log('');
|
|
3045
|
-
log(` ${C.gray}Known keys: gemini_api_key, lucylab_bearer${C.reset}`);
|
|
3120
|
+
log(` ${C.gray}Known keys: gemini_api_key, openrouter_api_key, lucylab_bearer${C.reset}`);
|
|
3046
3121
|
log(` ${C.gray}Config: ${CREDENTIALS_CONFIG_PATH}${C.reset}`);
|
|
3047
3122
|
log('');
|
|
3048
3123
|
}
|
|
@@ -3089,6 +3164,20 @@ async function credentialsSetup() {
|
|
|
3089
3164
|
dim('Kept existing LucyLab Bearer');
|
|
3090
3165
|
}
|
|
3091
3166
|
|
|
3167
|
+
log('');
|
|
3168
|
+
|
|
3169
|
+
// OpenRouter API Key
|
|
3170
|
+
log(`${C.gray} OpenRouter API Key for Premium Image Generation (GPT-5.4)${C.reset}`);
|
|
3171
|
+
log(`${C.gray} Get your key at: https://openrouter.ai/keys${C.reset}`);
|
|
3172
|
+
const openrouterKey = sanitize(await question(` ${C.yellow}OpenRouter API Key${config.openrouter_api_key ? ` [${config.openrouter_api_key.slice(0, 8)}...]` : ''}: ${C.reset}`));
|
|
3173
|
+
if (openrouterKey) {
|
|
3174
|
+
config.openrouter_api_key = openrouterKey;
|
|
3175
|
+
ok('OpenRouter API Key saved');
|
|
3176
|
+
} else if (config.openrouter_api_key) {
|
|
3177
|
+
dim('Kept existing OpenRouter API Key');
|
|
3178
|
+
}
|
|
3179
|
+
|
|
3180
|
+
log('');
|
|
3092
3181
|
credentialsSave(config);
|
|
3093
3182
|
log('');
|
|
3094
3183
|
ok(`Credentials saved to ${CREDENTIALS_CONFIG_PATH}`);
|
|
@@ -3918,6 +4007,15 @@ const [, , command, ...args] = process.argv;
|
|
|
3918
4007
|
case 'creds':
|
|
3919
4008
|
cmdCredentials(args);
|
|
3920
4009
|
break;
|
|
4010
|
+
case 'set-openrouter': {
|
|
4011
|
+
const key = args[0];
|
|
4012
|
+
if (!key) {
|
|
4013
|
+
err('Usage: awkit set-openrouter <api_key>');
|
|
4014
|
+
return;
|
|
4015
|
+
}
|
|
4016
|
+
cmdCredentials(['set', 'openrouter_api_key', key]);
|
|
4017
|
+
break;
|
|
4018
|
+
}
|
|
3921
4019
|
case 'serve':
|
|
3922
4020
|
cmdServe(args);
|
|
3923
4021
|
break;
|
|
@@ -3928,13 +4026,18 @@ const [, , command, ...args] = process.argv;
|
|
|
3928
4026
|
case 'gate':
|
|
3929
4027
|
cmdGate(args);
|
|
3930
4028
|
break;
|
|
3931
|
-
case 'obsidian':
|
|
3932
4029
|
case 'obs':
|
|
3933
4030
|
cmdObsidian(args);
|
|
3934
4031
|
break;
|
|
4032
|
+
case 'quota':
|
|
4033
|
+
cmdQuota(args);
|
|
4034
|
+
break;
|
|
3935
4035
|
case 'admin':
|
|
3936
4036
|
cmdAdmin();
|
|
3937
4037
|
break;
|
|
4038
|
+
case 'video':
|
|
4039
|
+
await cmdVideo(args);
|
|
4040
|
+
break;
|
|
3938
4041
|
case 'restart':
|
|
3939
4042
|
await cmdRestart();
|
|
3940
4043
|
break;
|
package/package.json
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@leejungkiin/awkit",
|
|
3
|
-
"version": "1.6.
|
|
3
|
+
"version": "1.6.8",
|
|
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
|
-
"@
|
|
42
|
-
"@
|
|
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
|
+
}
|
|
Binary file
|
|
@@ -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 };
|