@optima-chat/dev-skills 0.7.29 → 0.7.32
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/.claude/commands/trace-user.md +293 -0
- package/.claude/skills/grant-balance/SKILL.md +102 -0
- package/.claude/skills/grant-subscription/SKILL.md +16 -14
- package/.claude/skills/query-db/SKILL.md +4 -2
- package/.codex/skills/grant-balance/SKILL.md +101 -0
- package/.codex/skills/grant-subscription/SKILL.md +1 -1
- package/AGENTS.md +2 -2
- package/bin/cli.js +2 -2
- package/bin/helpers/grant-balance.ts +95 -0
- package/bin/helpers/grant-subscription.ts +17 -12
- package/bin/helpers/query-db.ts +6 -1
- package/dist/bin/helpers/generate-test-token.js +0 -0
- package/dist/bin/helpers/grant-balance.js +91 -0
- package/dist/bin/helpers/grant-subscription.js +17 -12
- package/dist/bin/helpers/query-db.js +6 -1
- package/dist/bin/helpers/show-env.js +0 -0
- package/package.json +2 -2
- package/.claude/settings.local.json +0 -51
- package/.claude/skills/grant-credits/SKILL.md +0 -105
- package/.codex/skills/grant-credits/SKILL.md +0 -28
- package/bin/helpers/grant-credits.ts +0 -65
- package/dist/bin/helpers/grant-credits.js +0 -71
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { getInfisicalConfig, getInfisicalToken, resolveUserId, connectBillingDB, escapeSQL } from './db-utils';
|
|
4
|
+
|
|
5
|
+
function parseArgs(args: string[]): { email: string; amountUsd: number; description: string | null; env: string } {
|
|
6
|
+
if (args.length === 0 || args[0] === '--help' || args[0] === '-h') {
|
|
7
|
+
console.log(`Usage: optima-grant-balance <email> --amount <usd> [options]
|
|
8
|
+
|
|
9
|
+
Add USD balance to a user's wallet (granted_balance_micros).
|
|
10
|
+
Used for promotional grants, compensation, referral rewards, etc.
|
|
11
|
+
|
|
12
|
+
Options:
|
|
13
|
+
--amount <usd> USD amount to grant (required, e.g. 5 for $5.00)
|
|
14
|
+
--description <text> Description for audit trail (optional)
|
|
15
|
+
--env <env> Environment: stage, prod (default: stage)
|
|
16
|
+
-h, --help Show this help
|
|
17
|
+
|
|
18
|
+
Examples:
|
|
19
|
+
optima-grant-balance user@example.com --amount 5 --env prod
|
|
20
|
+
optima-grant-balance user@example.com --amount 10 --description "Service outage compensation"`);
|
|
21
|
+
process.exit(0);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const email = args[0];
|
|
25
|
+
let amountUsd = 0;
|
|
26
|
+
let description: string | null = null;
|
|
27
|
+
let env = 'stage';
|
|
28
|
+
|
|
29
|
+
for (let i = 1; i < args.length; i++) {
|
|
30
|
+
if (args[i] === '--amount' && args[i + 1]) { amountUsd = parseFloat(args[++i]); }
|
|
31
|
+
else if (args[i] === '--description' && args[i + 1]) { description = args[++i]; }
|
|
32
|
+
else if (args[i] === '--env' && args[i + 1]) { env = args[++i]; }
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (!Number.isFinite(amountUsd) || amountUsd <= 0) {
|
|
36
|
+
console.error('--amount is required and must be > 0 (USD)');
|
|
37
|
+
process.exit(1);
|
|
38
|
+
}
|
|
39
|
+
if (!['stage', 'prod'].includes(env)) {
|
|
40
|
+
console.error('Env must be stage or prod (billing DB not available in CI)');
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return { email, amountUsd, description, env };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async function main() {
|
|
48
|
+
const { email, amountUsd, description, env } = parseArgs(process.argv.slice(2));
|
|
49
|
+
const infisicalConfig = getInfisicalConfig();
|
|
50
|
+
const token = getInfisicalToken(infisicalConfig);
|
|
51
|
+
|
|
52
|
+
// 1 USD = 1,000,000 micros. Round to integer micros.
|
|
53
|
+
const amountMicros = Math.round(amountUsd * 1_000_000);
|
|
54
|
+
console.log(`\n🎁 Granting $${amountUsd.toFixed(2)} to ${email} [${env.toUpperCase()}]\n`);
|
|
55
|
+
if (description) console.log(` Reason: ${description}`);
|
|
56
|
+
|
|
57
|
+
const userId = await resolveUserId(email, env, infisicalConfig, token);
|
|
58
|
+
const billing = await connectBillingDB(env, infisicalConfig, token);
|
|
59
|
+
const bq = billing.query;
|
|
60
|
+
|
|
61
|
+
const now = new Date().toISOString();
|
|
62
|
+
const safeUserId = escapeSQL(userId);
|
|
63
|
+
|
|
64
|
+
console.log(`Granting to wallet...`);
|
|
65
|
+
const txSQL = `
|
|
66
|
+
BEGIN;
|
|
67
|
+
|
|
68
|
+
-- Ensure wallet exists
|
|
69
|
+
INSERT INTO usd_wallets (id, user_id, balance_micros, reserved_micros, granted_balance_micros, created_at, updated_at)
|
|
70
|
+
VALUES (gen_random_uuid(), '${safeUserId}', 0, 0, 0, '${now}', '${now}')
|
|
71
|
+
ON CONFLICT (user_id) DO NOTHING;
|
|
72
|
+
|
|
73
|
+
-- Add to granted balance
|
|
74
|
+
UPDATE usd_wallets SET granted_balance_micros = granted_balance_micros + ${amountMicros}, updated_at = '${now}'
|
|
75
|
+
WHERE user_id = '${safeUserId}';
|
|
76
|
+
|
|
77
|
+
-- Record topup for audit trail (source='admin_grant' aligns with billing service convention)
|
|
78
|
+
INSERT INTO usd_wallet_topups (id, wallet_id, amount_micros, service_fee_micros, net_credit_micros, status, source, service_namespace, created_at, completed_at)
|
|
79
|
+
SELECT gen_random_uuid(), w.id, ${amountMicros}, 0, ${amountMicros}, 'completed', 'admin_grant', 'platform', '${now}', '${now}'
|
|
80
|
+
FROM usd_wallets w WHERE w.user_id = '${safeUserId}';
|
|
81
|
+
|
|
82
|
+
COMMIT;
|
|
83
|
+
`.trim();
|
|
84
|
+
bq(txSQL);
|
|
85
|
+
console.log(`✓ Granted $${amountUsd.toFixed(2)} to wallet`);
|
|
86
|
+
|
|
87
|
+
const balanceMicros = bq(`SELECT granted_balance_micros FROM usd_wallets WHERE user_id='${safeUserId}'`);
|
|
88
|
+
const balanceUsd = (parseInt(balanceMicros, 10) / 1000000).toFixed(2);
|
|
89
|
+
console.log(`\n✅ Done! ${email} now has $${balanceUsd} granted balance\n`);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
main().catch(error => {
|
|
93
|
+
console.error('\n❌ Error:', error.message);
|
|
94
|
+
process.exit(1);
|
|
95
|
+
});
|
|
@@ -53,9 +53,10 @@ async function main() {
|
|
|
53
53
|
|
|
54
54
|
const [planName, monthlyCreditsStr, sessionTokenLimitStr, weeklyTokenLimitStr] = planRow.split('|');
|
|
55
55
|
const monthlyCredits = parseInt(monthlyCreditsStr, 10);
|
|
56
|
+
const grantMicros = monthlyCredits * 10000; // 1 credit = $0.01 = 10,000 micros
|
|
56
57
|
const sessionTokenLimit = parseInt(sessionTokenLimitStr, 10);
|
|
57
58
|
const weeklyTokenLimit = parseInt(weeklyTokenLimitStr, 10);
|
|
58
|
-
console.log(`✓ Plan: ${planName} (
|
|
59
|
+
console.log(`✓ Plan: ${planName} (grant: $${(grantMicros / 1000000).toFixed(2)}, session: ${sessionTokenLimit.toLocaleString()}, weekly: ${weeklyTokenLimit.toLocaleString()})`);
|
|
59
60
|
|
|
60
61
|
// Execute all mutations in a single transaction
|
|
61
62
|
const now = new Date().toISOString();
|
|
@@ -77,18 +78,23 @@ BEGIN;
|
|
|
77
78
|
UPDATE subscriptions SET status='canceled', canceled_at='${now}'
|
|
78
79
|
WHERE user_id='${safeUserId}' AND status IN ('active','trialing');
|
|
79
80
|
|
|
80
|
-
-- Zero out old subscription credits
|
|
81
|
-
UPDATE credit_ledger SET remaining=0
|
|
82
|
-
WHERE user_id='${safeUserId}' AND type IN ('monthly_grant','subscription') AND remaining > 0;
|
|
83
|
-
|
|
84
81
|
-- Create new subscription
|
|
85
82
|
INSERT INTO subscriptions (id, user_id, plan_id, status, billing_interval, current_period_start, current_period_end, created_at, updated_at)
|
|
86
83
|
VALUES (concat('sub_gift_', substr(md5(random()::text), 1, 16)), '${safeUserId}', '${safePlan}', 'active', 'monthly', '${now}', '${periodEndISO}', '${now}', '${now}');
|
|
87
84
|
|
|
88
|
-
--
|
|
89
|
-
INSERT INTO
|
|
90
|
-
|
|
91
|
-
|
|
85
|
+
-- Ensure wallet exists (upsert)
|
|
86
|
+
INSERT INTO usd_wallets (id, user_id, balance_micros, reserved_micros, granted_balance_micros, created_at, updated_at)
|
|
87
|
+
VALUES (gen_random_uuid(), '${safeUserId}', 0, 0, 0, '${now}', '${now}')
|
|
88
|
+
ON CONFLICT (user_id) DO NOTHING;
|
|
89
|
+
|
|
90
|
+
-- Reset granted balance and set new grant
|
|
91
|
+
UPDATE usd_wallets SET granted_balance_micros = ${grantMicros}, updated_at = '${now}'
|
|
92
|
+
WHERE user_id = '${safeUserId}';
|
|
93
|
+
|
|
94
|
+
-- Record topup for audit trail
|
|
95
|
+
INSERT INTO usd_wallet_topups (id, wallet_id, amount_micros, service_fee_micros, net_credit_micros, status, source, service_namespace, created_at, completed_at)
|
|
96
|
+
SELECT gen_random_uuid(), w.id, ${grantMicros}, 0, ${grantMicros}, 'completed', 'subscription_grant', 'platform', '${now}', '${now}'
|
|
97
|
+
FROM usd_wallets w WHERE w.user_id = '${safeUserId}';
|
|
92
98
|
|
|
93
99
|
-- Update existing active session quota, or insert new one if none exists
|
|
94
100
|
UPDATE token_quotas SET plan_id='${safePlan}', monthly_limit=${sessionTokenLimit}, updated_at='${now}'
|
|
@@ -112,10 +118,9 @@ COMMIT;
|
|
|
112
118
|
bq(txSQL);
|
|
113
119
|
|
|
114
120
|
console.log('✓ Old subscriptions canceled');
|
|
115
|
-
console.log('✓ Old credits cleared');
|
|
116
121
|
console.log(`✓ ${planName} subscription created (expires: ${periodEnd.toLocaleDateString()})`);
|
|
117
|
-
if (
|
|
118
|
-
console.log(`✓ ${monthlyCredits} credits
|
|
122
|
+
if (grantMicros > 0) {
|
|
123
|
+
console.log(`✓ Wallet granted $${(grantMicros / 1000000).toFixed(2)} (${monthlyCredits} credits equivalent)`);
|
|
119
124
|
}
|
|
120
125
|
console.log(`✓ Token quotas updated (session: ${sessionTokenLimit.toLocaleString()}, weekly: ${weeklyTokenLimit.toLocaleString()})`);
|
|
121
126
|
|
package/bin/helpers/query-db.ts
CHANGED
|
@@ -43,6 +43,11 @@ const SERVICE_DB_MAP = {
|
|
|
43
43
|
stage: { userKey: 'AI_SHELL_DB_USER', passwordKey: 'AI_SHELL_DB_PASSWORD', database: 'optima_shell' },
|
|
44
44
|
prod: { userKey: 'AI_SHELL_DB_USER', passwordKey: 'AI_SHELL_DB_PASSWORD', database: 'optima_ai_shell' }
|
|
45
45
|
},
|
|
46
|
+
'gateway-core': {
|
|
47
|
+
ci: null, // CI 环境 gateway-core 不带 DB(本地 JSONL-only 模式)
|
|
48
|
+
stage: { databaseUrlPath: '/services/gateway-core', databaseUrlKey: 'DATABASE_URL' },
|
|
49
|
+
prod: { databaseUrlPath: '/services/gateway-core', databaseUrlKey: 'DATABASE_URL' }
|
|
50
|
+
},
|
|
46
51
|
'optima-logistics': {
|
|
47
52
|
ci: null,
|
|
48
53
|
stage: { userKey: 'LOGISTICS_DB_USER', passwordKey: 'LOGISTICS_DB_PASSWORD', database: 'optima_stage_logistics' },
|
|
@@ -229,7 +234,7 @@ async function main() {
|
|
|
229
234
|
if (args.length < 2) {
|
|
230
235
|
console.error('Usage: query-db.ts <service> <sql> [environment]');
|
|
231
236
|
console.error('');
|
|
232
|
-
console.error('Services: commerce-backend, user-auth, agentic-chat, bi-backend, session-gateway, optima-logistics, billing, ads-backend, amazon-backend, browser-backend, shopify-backend, optima-generation, optima-sentinel');
|
|
237
|
+
console.error('Services: commerce-backend, user-auth, agentic-chat, bi-backend, session-gateway, gateway-core, optima-logistics, billing, ads-backend, amazon-backend, browser-backend, shopify-backend, optima-generation, optima-sentinel');
|
|
233
238
|
console.error('Environments: ci (default), stage, prod');
|
|
234
239
|
console.error('');
|
|
235
240
|
console.error('Example: query-db.ts user-auth "SELECT COUNT(*) FROM users" prod');
|
|
File without changes
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
const db_utils_1 = require("./db-utils");
|
|
5
|
+
function parseArgs(args) {
|
|
6
|
+
if (args.length === 0 || args[0] === '--help' || args[0] === '-h') {
|
|
7
|
+
console.log(`Usage: optima-grant-balance <email> --amount <usd> [options]
|
|
8
|
+
|
|
9
|
+
Add USD balance to a user's wallet (granted_balance_micros).
|
|
10
|
+
Used for promotional grants, compensation, referral rewards, etc.
|
|
11
|
+
|
|
12
|
+
Options:
|
|
13
|
+
--amount <usd> USD amount to grant (required, e.g. 5 for $5.00)
|
|
14
|
+
--description <text> Description for audit trail (optional)
|
|
15
|
+
--env <env> Environment: stage, prod (default: stage)
|
|
16
|
+
-h, --help Show this help
|
|
17
|
+
|
|
18
|
+
Examples:
|
|
19
|
+
optima-grant-balance user@example.com --amount 5 --env prod
|
|
20
|
+
optima-grant-balance user@example.com --amount 10 --description "Service outage compensation"`);
|
|
21
|
+
process.exit(0);
|
|
22
|
+
}
|
|
23
|
+
const email = args[0];
|
|
24
|
+
let amountUsd = 0;
|
|
25
|
+
let description = null;
|
|
26
|
+
let env = 'stage';
|
|
27
|
+
for (let i = 1; i < args.length; i++) {
|
|
28
|
+
if (args[i] === '--amount' && args[i + 1]) {
|
|
29
|
+
amountUsd = parseFloat(args[++i]);
|
|
30
|
+
}
|
|
31
|
+
else if (args[i] === '--description' && args[i + 1]) {
|
|
32
|
+
description = args[++i];
|
|
33
|
+
}
|
|
34
|
+
else if (args[i] === '--env' && args[i + 1]) {
|
|
35
|
+
env = args[++i];
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
if (!Number.isFinite(amountUsd) || amountUsd <= 0) {
|
|
39
|
+
console.error('--amount is required and must be > 0 (USD)');
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
42
|
+
if (!['stage', 'prod'].includes(env)) {
|
|
43
|
+
console.error('Env must be stage or prod (billing DB not available in CI)');
|
|
44
|
+
process.exit(1);
|
|
45
|
+
}
|
|
46
|
+
return { email, amountUsd, description, env };
|
|
47
|
+
}
|
|
48
|
+
async function main() {
|
|
49
|
+
const { email, amountUsd, description, env } = parseArgs(process.argv.slice(2));
|
|
50
|
+
const infisicalConfig = (0, db_utils_1.getInfisicalConfig)();
|
|
51
|
+
const token = (0, db_utils_1.getInfisicalToken)(infisicalConfig);
|
|
52
|
+
// 1 USD = 1,000,000 micros. Round to integer micros.
|
|
53
|
+
const amountMicros = Math.round(amountUsd * 1000000);
|
|
54
|
+
console.log(`\n🎁 Granting $${amountUsd.toFixed(2)} to ${email} [${env.toUpperCase()}]\n`);
|
|
55
|
+
if (description)
|
|
56
|
+
console.log(` Reason: ${description}`);
|
|
57
|
+
const userId = await (0, db_utils_1.resolveUserId)(email, env, infisicalConfig, token);
|
|
58
|
+
const billing = await (0, db_utils_1.connectBillingDB)(env, infisicalConfig, token);
|
|
59
|
+
const bq = billing.query;
|
|
60
|
+
const now = new Date().toISOString();
|
|
61
|
+
const safeUserId = (0, db_utils_1.escapeSQL)(userId);
|
|
62
|
+
console.log(`Granting to wallet...`);
|
|
63
|
+
const txSQL = `
|
|
64
|
+
BEGIN;
|
|
65
|
+
|
|
66
|
+
-- Ensure wallet exists
|
|
67
|
+
INSERT INTO usd_wallets (id, user_id, balance_micros, reserved_micros, granted_balance_micros, created_at, updated_at)
|
|
68
|
+
VALUES (gen_random_uuid(), '${safeUserId}', 0, 0, 0, '${now}', '${now}')
|
|
69
|
+
ON CONFLICT (user_id) DO NOTHING;
|
|
70
|
+
|
|
71
|
+
-- Add to granted balance
|
|
72
|
+
UPDATE usd_wallets SET granted_balance_micros = granted_balance_micros + ${amountMicros}, updated_at = '${now}'
|
|
73
|
+
WHERE user_id = '${safeUserId}';
|
|
74
|
+
|
|
75
|
+
-- Record topup for audit trail (source='admin_grant' aligns with billing service convention)
|
|
76
|
+
INSERT INTO usd_wallet_topups (id, wallet_id, amount_micros, service_fee_micros, net_credit_micros, status, source, service_namespace, created_at, completed_at)
|
|
77
|
+
SELECT gen_random_uuid(), w.id, ${amountMicros}, 0, ${amountMicros}, 'completed', 'admin_grant', 'platform', '${now}', '${now}'
|
|
78
|
+
FROM usd_wallets w WHERE w.user_id = '${safeUserId}';
|
|
79
|
+
|
|
80
|
+
COMMIT;
|
|
81
|
+
`.trim();
|
|
82
|
+
bq(txSQL);
|
|
83
|
+
console.log(`✓ Granted $${amountUsd.toFixed(2)} to wallet`);
|
|
84
|
+
const balanceMicros = bq(`SELECT granted_balance_micros FROM usd_wallets WHERE user_id='${safeUserId}'`);
|
|
85
|
+
const balanceUsd = (parseInt(balanceMicros, 10) / 1000000).toFixed(2);
|
|
86
|
+
console.log(`\n✅ Done! ${email} now has $${balanceUsd} granted balance\n`);
|
|
87
|
+
}
|
|
88
|
+
main().catch(error => {
|
|
89
|
+
console.error('\n❌ Error:', error.message);
|
|
90
|
+
process.exit(1);
|
|
91
|
+
});
|
|
@@ -59,9 +59,10 @@ async function main() {
|
|
|
59
59
|
}
|
|
60
60
|
const [planName, monthlyCreditsStr, sessionTokenLimitStr, weeklyTokenLimitStr] = planRow.split('|');
|
|
61
61
|
const monthlyCredits = parseInt(monthlyCreditsStr, 10);
|
|
62
|
+
const grantMicros = monthlyCredits * 10000; // 1 credit = $0.01 = 10,000 micros
|
|
62
63
|
const sessionTokenLimit = parseInt(sessionTokenLimitStr, 10);
|
|
63
64
|
const weeklyTokenLimit = parseInt(weeklyTokenLimitStr, 10);
|
|
64
|
-
console.log(`✓ Plan: ${planName} (
|
|
65
|
+
console.log(`✓ Plan: ${planName} (grant: $${(grantMicros / 1000000).toFixed(2)}, session: ${sessionTokenLimit.toLocaleString()}, weekly: ${weeklyTokenLimit.toLocaleString()})`);
|
|
65
66
|
// Execute all mutations in a single transaction
|
|
66
67
|
const now = new Date().toISOString();
|
|
67
68
|
const periodEnd = new Date();
|
|
@@ -80,18 +81,23 @@ BEGIN;
|
|
|
80
81
|
UPDATE subscriptions SET status='canceled', canceled_at='${now}'
|
|
81
82
|
WHERE user_id='${safeUserId}' AND status IN ('active','trialing');
|
|
82
83
|
|
|
83
|
-
-- Zero out old subscription credits
|
|
84
|
-
UPDATE credit_ledger SET remaining=0
|
|
85
|
-
WHERE user_id='${safeUserId}' AND type IN ('monthly_grant','subscription') AND remaining > 0;
|
|
86
|
-
|
|
87
84
|
-- Create new subscription
|
|
88
85
|
INSERT INTO subscriptions (id, user_id, plan_id, status, billing_interval, current_period_start, current_period_end, created_at, updated_at)
|
|
89
86
|
VALUES (concat('sub_gift_', substr(md5(random()::text), 1, 16)), '${safeUserId}', '${safePlan}', 'active', 'monthly', '${now}', '${periodEndISO}', '${now}', '${now}');
|
|
90
87
|
|
|
91
|
-
--
|
|
92
|
-
INSERT INTO
|
|
93
|
-
|
|
94
|
-
|
|
88
|
+
-- Ensure wallet exists (upsert)
|
|
89
|
+
INSERT INTO usd_wallets (id, user_id, balance_micros, reserved_micros, granted_balance_micros, created_at, updated_at)
|
|
90
|
+
VALUES (gen_random_uuid(), '${safeUserId}', 0, 0, 0, '${now}', '${now}')
|
|
91
|
+
ON CONFLICT (user_id) DO NOTHING;
|
|
92
|
+
|
|
93
|
+
-- Reset granted balance and set new grant
|
|
94
|
+
UPDATE usd_wallets SET granted_balance_micros = ${grantMicros}, updated_at = '${now}'
|
|
95
|
+
WHERE user_id = '${safeUserId}';
|
|
96
|
+
|
|
97
|
+
-- Record topup for audit trail
|
|
98
|
+
INSERT INTO usd_wallet_topups (id, wallet_id, amount_micros, service_fee_micros, net_credit_micros, status, source, service_namespace, created_at, completed_at)
|
|
99
|
+
SELECT gen_random_uuid(), w.id, ${grantMicros}, 0, ${grantMicros}, 'completed', 'subscription_grant', 'platform', '${now}', '${now}'
|
|
100
|
+
FROM usd_wallets w WHERE w.user_id = '${safeUserId}';
|
|
95
101
|
|
|
96
102
|
-- Update existing active session quota, or insert new one if none exists
|
|
97
103
|
UPDATE token_quotas SET plan_id='${safePlan}', monthly_limit=${sessionTokenLimit}, updated_at='${now}'
|
|
@@ -113,10 +119,9 @@ COMMIT;
|
|
|
113
119
|
`.trim();
|
|
114
120
|
bq(txSQL);
|
|
115
121
|
console.log('✓ Old subscriptions canceled');
|
|
116
|
-
console.log('✓ Old credits cleared');
|
|
117
122
|
console.log(`✓ ${planName} subscription created (expires: ${periodEnd.toLocaleDateString()})`);
|
|
118
|
-
if (
|
|
119
|
-
console.log(`✓ ${monthlyCredits} credits
|
|
123
|
+
if (grantMicros > 0) {
|
|
124
|
+
console.log(`✓ Wallet granted $${(grantMicros / 1000000).toFixed(2)} (${monthlyCredits} credits equivalent)`);
|
|
120
125
|
}
|
|
121
126
|
console.log(`✓ Token quotas updated (session: ${sessionTokenLimit.toLocaleString()}, weekly: ${weeklyTokenLimit.toLocaleString()})`);
|
|
122
127
|
console.log(`\n✅ Done! ${email} now has ${planName} plan until ${periodEnd.toLocaleDateString()}\n`);
|
|
@@ -62,6 +62,11 @@ const SERVICE_DB_MAP = {
|
|
|
62
62
|
stage: { userKey: 'AI_SHELL_DB_USER', passwordKey: 'AI_SHELL_DB_PASSWORD', database: 'optima_shell' },
|
|
63
63
|
prod: { userKey: 'AI_SHELL_DB_USER', passwordKey: 'AI_SHELL_DB_PASSWORD', database: 'optima_ai_shell' }
|
|
64
64
|
},
|
|
65
|
+
'gateway-core': {
|
|
66
|
+
ci: null, // CI 环境 gateway-core 不带 DB(本地 JSONL-only 模式)
|
|
67
|
+
stage: { databaseUrlPath: '/services/gateway-core', databaseUrlKey: 'DATABASE_URL' },
|
|
68
|
+
prod: { databaseUrlPath: '/services/gateway-core', databaseUrlKey: 'DATABASE_URL' }
|
|
69
|
+
},
|
|
65
70
|
'optima-logistics': {
|
|
66
71
|
ci: null,
|
|
67
72
|
stage: { userKey: 'LOGISTICS_DB_USER', passwordKey: 'LOGISTICS_DB_PASSWORD', database: 'optima_stage_logistics' },
|
|
@@ -219,7 +224,7 @@ async function main() {
|
|
|
219
224
|
if (args.length < 2) {
|
|
220
225
|
console.error('Usage: query-db.ts <service> <sql> [environment]');
|
|
221
226
|
console.error('');
|
|
222
|
-
console.error('Services: commerce-backend, user-auth, agentic-chat, bi-backend, session-gateway, optima-logistics, billing, ads-backend, amazon-backend, browser-backend, shopify-backend, optima-generation, optima-sentinel');
|
|
227
|
+
console.error('Services: commerce-backend, user-auth, agentic-chat, bi-backend, session-gateway, gateway-core, optima-logistics, billing, ads-backend, amazon-backend, browser-backend, shopify-backend, optima-generation, optima-sentinel');
|
|
223
228
|
console.error('Environments: ci (default), stage, prod');
|
|
224
229
|
console.error('');
|
|
225
230
|
console.error('Example: query-db.ts user-auth "SELECT COUNT(*) FROM users" prod');
|
|
File without changes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@optima-chat/dev-skills",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.32",
|
|
4
4
|
"description": "Claude Code Skills for Optima development team - cross-environment collaboration tools",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"optima-generate-test-token": "dist/bin/helpers/generate-test-token.js",
|
|
10
10
|
"optima-show-env": "dist/bin/helpers/show-env.js",
|
|
11
11
|
"optima-grant-subscription": "dist/bin/helpers/grant-subscription.js",
|
|
12
|
-
"optima-grant-
|
|
12
|
+
"optima-grant-balance": "dist/bin/helpers/grant-balance.js"
|
|
13
13
|
},
|
|
14
14
|
"scripts": {
|
|
15
15
|
"postinstall": "node scripts/install.js",
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"permissions": {
|
|
3
|
-
"allow": [
|
|
4
|
-
"WebSearch",
|
|
5
|
-
"WebFetch(domain:code.claude.com)",
|
|
6
|
-
"WebFetch(domain:platform.claude.com)",
|
|
7
|
-
"WebFetch(domain:github.com)",
|
|
8
|
-
"Bash(gh repo view:*)",
|
|
9
|
-
"Bash(gh repo clone:*)",
|
|
10
|
-
"Bash(gh repo list:*)",
|
|
11
|
-
"Read(//private/tmp/optima-docs/**)",
|
|
12
|
-
"Read(//tmp/optima-docs/**)",
|
|
13
|
-
"Bash(git init:*)",
|
|
14
|
-
"Bash(gh repo create:*)",
|
|
15
|
-
"Read(//private/tmp/optima-workspace/**)",
|
|
16
|
-
"Read(//tmp/optima-workspace/**)",
|
|
17
|
-
"Read(//tmp/optima-workspace/.claude/commands/**)",
|
|
18
|
-
"Bash(git add:*)",
|
|
19
|
-
"Bash(git push:*)",
|
|
20
|
-
"Bash(find:*)",
|
|
21
|
-
"Bash(git commit:*)",
|
|
22
|
-
"Bash(aws logs get-log-events:*)",
|
|
23
|
-
"Bash(npm install:*)",
|
|
24
|
-
"Bash(optima-dev-skills:*)",
|
|
25
|
-
"Bash(optima-generate-test-token:*)",
|
|
26
|
-
"Bash(optima-query-db:*)",
|
|
27
|
-
"Bash(gh variable set:*)",
|
|
28
|
-
"Bash(npm publish:*)",
|
|
29
|
-
"Bash(python3:*)",
|
|
30
|
-
"Bash(gh api:*)",
|
|
31
|
-
"Bash(curl -s http://auth.optima.chat/openapi.json)",
|
|
32
|
-
"Bash(curl -s https://auth.optima.chat/openapi.json)",
|
|
33
|
-
"Bash(cat:*)",
|
|
34
|
-
"Bash(node /Users/verypro/optima-dev-skills/scripts/install.js:*)",
|
|
35
|
-
"Bash(aws logs tail:*)",
|
|
36
|
-
"Bash(grep:*)",
|
|
37
|
-
"Bash(npm view:*)",
|
|
38
|
-
"Bash(npm version:*)",
|
|
39
|
-
"Bash(git checkout:*)",
|
|
40
|
-
"Bash(git pull:*)",
|
|
41
|
-
"Bash(node scripts/install.js:*)",
|
|
42
|
-
"Bash(gh issue:*)",
|
|
43
|
-
"Bash(npm run:*)",
|
|
44
|
-
"Bash(gh pr:*)",
|
|
45
|
-
"Bash(node:*)",
|
|
46
|
-
"Bash(echo \"exit: $?\")"
|
|
47
|
-
],
|
|
48
|
-
"deny": [],
|
|
49
|
-
"ask": []
|
|
50
|
-
}
|
|
51
|
-
}
|
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: "grant-credits"
|
|
3
|
-
description: "当用户请求赠送 credits、充值积分、grant credits、加 credits、奖励积分、补偿 credits、推荐奖励时,使用此技能。支持 Stage、Prod 两个环境。"
|
|
4
|
-
allowed-tools: ["Bash"]
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
# 赠送 Credits
|
|
8
|
-
|
|
9
|
-
当你需要为用户赠送额外 credits 时,使用这个场景。
|
|
10
|
-
|
|
11
|
-
## 执行方式:使用 CLI 工具
|
|
12
|
-
|
|
13
|
-
**重要**:使用 `optima-grant-credits` CLI 工具:
|
|
14
|
-
|
|
15
|
-
```bash
|
|
16
|
-
optima-grant-credits <email> --amount <n> [options]
|
|
17
|
-
```
|
|
18
|
-
|
|
19
|
-
**为什么使用 CLI 工具**:
|
|
20
|
-
- 自动通过 email 查找 userId(跨 user-auth 数据库)
|
|
21
|
-
- 自动处理 SSH 隧道和数据库连接
|
|
22
|
-
- 不会影响现有订阅和 credits(纯追加)
|
|
23
|
-
- 一条命令完成操作
|
|
24
|
-
|
|
25
|
-
## 适用情况
|
|
26
|
-
|
|
27
|
-
- 赠送额外 credits(奖励、补偿、推广等)
|
|
28
|
-
- 推荐奖励 credits
|
|
29
|
-
- 运营活动发放 credits
|
|
30
|
-
- 客户补偿
|
|
31
|
-
|
|
32
|
-
## 快速操作
|
|
33
|
-
|
|
34
|
-
```bash
|
|
35
|
-
# 赠送 100 bonus credits,Stage 环境(默认)
|
|
36
|
-
optima-grant-credits user@example.com --amount 100
|
|
37
|
-
|
|
38
|
-
# 赠送 500 bonus credits,Prod 环境
|
|
39
|
-
optima-grant-credits user@example.com --amount 500 --env prod
|
|
40
|
-
|
|
41
|
-
# 推荐奖励 credits
|
|
42
|
-
optima-grant-credits user@example.com --amount 200 --type referral --env prod
|
|
43
|
-
|
|
44
|
-
# 自定义描述
|
|
45
|
-
optima-grant-credits user@example.com --amount 300 --description "客户补偿 - 服务中断" --env prod
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
### 参数说明
|
|
49
|
-
|
|
50
|
-
| 参数 | 说明 | 默认值 |
|
|
51
|
-
|------|------|--------|
|
|
52
|
-
| `<email>` | 用户邮箱(必填) | - |
|
|
53
|
-
| `--amount <n>` | Credits 数量(必填,>=1) | - |
|
|
54
|
-
| `--type <type>` | 类型:bonus, referral | bonus |
|
|
55
|
-
| `--description <text>` | 描述(可选) | Auto-generated |
|
|
56
|
-
| `--env <env>` | 环境:stage, prod | stage |
|
|
57
|
-
|
|
58
|
-
## 与 grant-subscription 的区别
|
|
59
|
-
|
|
60
|
-
| | grant-credits | grant-subscription |
|
|
61
|
-
|---|---|---|
|
|
62
|
-
| 作用 | 追加 credits | 开通/切换订阅计划 |
|
|
63
|
-
| 现有 credits | 不影响 | 清零后重新授予 |
|
|
64
|
-
| 现有订阅 | 不影响 | 取消旧的,创建新的 |
|
|
65
|
-
| Token quota | 不影响 | 按计划更新 |
|
|
66
|
-
| 适用场景 | 奖励、补偿、推广 | 开通会员、升级计划 |
|
|
67
|
-
|
|
68
|
-
## 常见使用场景
|
|
69
|
-
|
|
70
|
-
### 场景 1:奖励 credits
|
|
71
|
-
|
|
72
|
-
**用户请求**:"给 xxx@gmail.com 加 200 credits"
|
|
73
|
-
|
|
74
|
-
```bash
|
|
75
|
-
optima-grant-credits xxx@gmail.com --amount 200 --env prod
|
|
76
|
-
```
|
|
77
|
-
|
|
78
|
-
### 场景 2:推荐奖励
|
|
79
|
-
|
|
80
|
-
**用户请求**:"xxx 推荐了新用户,给他 referral 奖励 300"
|
|
81
|
-
|
|
82
|
-
```bash
|
|
83
|
-
optima-grant-credits xxx@gmail.com --amount 300 --type referral --env prod
|
|
84
|
-
```
|
|
85
|
-
|
|
86
|
-
### 场景 3:客户补偿
|
|
87
|
-
|
|
88
|
-
**用户请求**:"服务出了问题,补偿 xxx 500 credits"
|
|
89
|
-
|
|
90
|
-
```bash
|
|
91
|
-
optima-grant-credits xxx@gmail.com --amount 500 --description "服务中断补偿" --env prod
|
|
92
|
-
```
|
|
93
|
-
|
|
94
|
-
## 安全提醒
|
|
95
|
-
|
|
96
|
-
1. **Stage 优先**:默认操作 Stage 环境
|
|
97
|
-
2. **Prod 谨慎**:操作 Prod 前确认邮箱和数量
|
|
98
|
-
3. **纯追加**:不会影响现有 credits 和订阅
|
|
99
|
-
4. **无过期**:bonus/referral credits 默认不设过期时间
|
|
100
|
-
|
|
101
|
-
## 相关命令
|
|
102
|
-
|
|
103
|
-
- `optima-grant-credits` - 赠送 credits(主要方式)
|
|
104
|
-
- `optima-grant-subscription` - 开通订阅计划
|
|
105
|
-
- `optima-query-db` - 查询数据库验证结果
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: "grant-credits"
|
|
3
|
-
description: "Use when the user wants to add bonus or referral credits to an Optima user without changing their subscription."
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Grant Credits
|
|
7
|
-
|
|
8
|
-
Use this skill when the user asks to add credits directly.
|
|
9
|
-
|
|
10
|
-
## Preferred Command
|
|
11
|
-
|
|
12
|
-
```bash
|
|
13
|
-
optima-grant-credits <email> --amount <n> [options]
|
|
14
|
-
```
|
|
15
|
-
|
|
16
|
-
## Examples
|
|
17
|
-
|
|
18
|
-
```bash
|
|
19
|
-
optima-grant-credits user@example.com --amount 100
|
|
20
|
-
optima-grant-credits user@example.com --amount 500 --env prod
|
|
21
|
-
optima-grant-credits user@example.com --amount 300 --type referral --env prod
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
## Guidance
|
|
25
|
-
|
|
26
|
-
- Default to `stage`.
|
|
27
|
-
- Confirm the email and amount before using `prod`.
|
|
28
|
-
- This operation appends credits and does not replace the user's subscription.
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import { getInfisicalConfig, getInfisicalToken, resolveUserId, connectBillingDB, escapeSQL } from './db-utils';
|
|
4
|
-
|
|
5
|
-
function parseArgs(args: string[]): { email: string; amount: number; type: string; description: string | null; env: string } {
|
|
6
|
-
if (args.length === 0 || args[0] === '--help' || args[0] === '-h') {
|
|
7
|
-
console.log(`Usage: optima-grant-credits <email> --amount <n> [options]
|
|
8
|
-
|
|
9
|
-
Options:
|
|
10
|
-
--amount <n> Credits to grant (required)
|
|
11
|
-
--type <type> Credit type: bonus, referral (default: bonus)
|
|
12
|
-
--description <text> Description (optional)
|
|
13
|
-
--env <env> Environment: stage, prod (default: stage)
|
|
14
|
-
-h, --help Show this help`);
|
|
15
|
-
process.exit(0);
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
const email = args[0];
|
|
19
|
-
let amount = 0;
|
|
20
|
-
let type = 'bonus';
|
|
21
|
-
let description: string | null = null;
|
|
22
|
-
let env = 'stage';
|
|
23
|
-
|
|
24
|
-
for (let i = 1; i < args.length; i++) {
|
|
25
|
-
if (args[i] === '--amount' && args[i + 1]) { amount = parseInt(args[++i], 10); }
|
|
26
|
-
else if (args[i] === '--type' && args[i + 1]) { type = args[++i]; }
|
|
27
|
-
else if (args[i] === '--description' && args[i + 1]) { description = args[++i]; }
|
|
28
|
-
else if (args[i] === '--env' && args[i + 1]) { env = args[++i]; }
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
if (amount < 1) { console.error('--amount is required and must be >= 1'); process.exit(1); }
|
|
32
|
-
if (!['bonus', 'referral'].includes(type)) { console.error(`Unknown type: ${type}. Available: bonus, referral`); process.exit(1); }
|
|
33
|
-
if (!['stage', 'prod'].includes(env)) { console.error('Env must be stage or prod (billing DB not available in CI)'); process.exit(1); }
|
|
34
|
-
|
|
35
|
-
return { email, amount, type, description, env };
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
async function main() {
|
|
39
|
-
const { email, amount, type, description, env } = parseArgs(process.argv.slice(2));
|
|
40
|
-
const infisicalConfig = getInfisicalConfig();
|
|
41
|
-
const token = getInfisicalToken(infisicalConfig);
|
|
42
|
-
|
|
43
|
-
console.log(`\n🎁 Granting ${amount} ${type} credits to ${email} [${env.toUpperCase()}]\n`);
|
|
44
|
-
|
|
45
|
-
const userId = await resolveUserId(email, env, infisicalConfig, token);
|
|
46
|
-
const billing = await connectBillingDB(env, infisicalConfig, token);
|
|
47
|
-
const bq = billing.query;
|
|
48
|
-
|
|
49
|
-
const now = new Date().toISOString();
|
|
50
|
-
const safeUserId = escapeSQL(userId);
|
|
51
|
-
const safeType = escapeSQL(type);
|
|
52
|
-
const safeDesc = escapeSQL(description || `Admin ${type} credit grant`);
|
|
53
|
-
|
|
54
|
-
console.log(`Inserting ${amount} ${type} credits...`);
|
|
55
|
-
const ledgerId = bq(`INSERT INTO credit_ledger (id, user_id, type, description, initial_amount, remaining, created_at) VALUES (concat('crd_${safeType}_', substr(md5(random()::text), 1, 16)), '${safeUserId}', '${safeType}', '${safeDesc}', ${amount}, ${amount}, '${now}') RETURNING id`);
|
|
56
|
-
console.log(`✓ Credits granted (ledger ID: ${ledgerId})`);
|
|
57
|
-
|
|
58
|
-
const balance = bq(`SELECT COALESCE(SUM(remaining), 0) FROM credit_ledger WHERE user_id='${safeUserId}' AND remaining > 0 AND (expires_at IS NULL OR expires_at > NOW())`);
|
|
59
|
-
console.log(`\n✅ Done! ${email} now has ${balance} total credits\n`);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
main().catch(error => {
|
|
63
|
-
console.error('\n❌ Error:', error.message);
|
|
64
|
-
process.exit(1);
|
|
65
|
-
});
|