@blod-nm/1kuan-act-01-cli 0.1.2 → 0.1.4
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/README.md +13 -1
- package/package.json +1 -1
- package/src/main.js +109 -12
- package/src/mcp.js +2 -1
- package/src/redpacket.js +5 -5
- package/src/skill.js +12 -5
- package/src/version.js +6 -0
package/README.md
CHANGED
|
@@ -30,15 +30,27 @@ act-01 config import "act01w.v1.xxxx" # prompts for the passphrase used at gen
|
|
|
30
30
|
|
|
31
31
|
Transfer strings are AES-256-GCM encrypted; the passphrase never travels with the string.
|
|
32
32
|
|
|
33
|
+
## One-shot setup
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
ACT01_EXPORT_PASS="<passphrase>" act-01 setup --install ~/.claude/skills "<config-string>"
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Installs the skill doc, imports the config, and prints the balance in one command
|
|
40
|
+
(passphrase via env; config string also accepted on stdin or `ACT01_IMPORT_STRING`).
|
|
41
|
+
`--install <dir>` writes `<dir>/act-01-ops/SKILL.md`.
|
|
42
|
+
|
|
33
43
|
## Operations
|
|
34
44
|
|
|
35
45
|
```bash
|
|
36
|
-
act-01
|
|
46
|
+
act-01 redpacket check --mode random --total 10000 --count 50 --per-user 2 --min 100 --max 500
|
|
47
|
+
act-01 redpacket create --mode random --total 10000 --count 50 --per-user 2 --min 100 --max 500 --yes
|
|
37
48
|
```
|
|
38
49
|
|
|
39
50
|
- Amounts are integer units, no conversion
|
|
40
51
|
- Pre-flight balance check runs before execution; insufficient balance is rejected locally
|
|
41
52
|
- Interactive confirm card lists every parameter before the atomic database function call
|
|
53
|
+
- `create` is non-interactive only with `--yes` (otherwise it asks for confirmation)
|
|
42
54
|
- `--dry-run` prints the function call without executing; `--json` for machine output
|
|
43
55
|
|
|
44
56
|
## MCP (AI client integration)
|
package/package.json
CHANGED
package/src/main.js
CHANGED
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import { parseArgs } from 'node:util'
|
|
6
|
+
import { mkdirSync, writeFileSync } from 'node:fs'
|
|
7
|
+
import { VERSION } from './version.js'
|
|
6
8
|
import { join } from 'node:path'
|
|
7
9
|
import { configPath, expandPath, loadConfig, saveConfig, decodeConfigString, encryptConfig } from './store.js'
|
|
8
10
|
import { ACCOUNT_SQL, execPgsql, resolveApikey, gatewayBase } from './tcb.js'
|
|
@@ -10,7 +12,6 @@ import { buildCreateSql, confirmSummary, normalizeCreateRedpacket } from './redp
|
|
|
10
12
|
import { SKILL_MD, SKILL_NAME } from './skill.js'
|
|
11
13
|
import { startMcp } from './mcp.js'
|
|
12
14
|
|
|
13
|
-
const VERSION = '0.1.0'
|
|
14
15
|
|
|
15
16
|
const USAGE = `act-01 —— operations CLI(invoke preset database functions)
|
|
16
17
|
|
|
@@ -22,9 +23,10 @@ const USAGE = `act-01 —— operations CLI(invoke preset database functions)
|
|
|
22
23
|
config set apiKey 写入 API Key(值走 stdin;不放命令行)
|
|
23
24
|
config test 连接测试(SELECT 1,验证 envId/apiKey)
|
|
24
25
|
config export 导出加密配置串(口令交互输入或 ACT01_EXPORT_PASS)
|
|
25
|
-
config import <串|--stdin>
|
|
26
|
+
config import <串|--stdin> [--pass <口令>] 导入配置串(加密串需口令;自动识别 三种格式)
|
|
27
|
+
setup <串|--stdin> [--install <技能根目录>] 一条命令:装技能 + 导配置 + 查余额
|
|
26
28
|
mcp 启动 MCP 服务(stdio;接入 Claude 等 AI 客户端)
|
|
27
|
-
skill 输出 agent 技能文档(--install <
|
|
29
|
+
skill 输出 agent 技能文档(--install <技能根目录> 装成 <root>/act-01-ops/SKILL.md)
|
|
28
30
|
balance account balance
|
|
29
31
|
redpacket create create a record(pre-flight balance check, atomic deduct)
|
|
30
32
|
redpacket check validate params(side-effect free)
|
|
@@ -150,6 +152,23 @@ async function cmdConfig(argv) {
|
|
|
150
152
|
throw err
|
|
151
153
|
}
|
|
152
154
|
|
|
155
|
+
if (sub === 'test') {
|
|
156
|
+
const config = loadConfig()
|
|
157
|
+
const apikey = resolveApikey(config, undefined)
|
|
158
|
+
if (!config.envId || !apikey) {
|
|
159
|
+
const err = new Error('配置不完整:先 config set envId 和 apiKey')
|
|
160
|
+
err.exitCode = 2
|
|
161
|
+
throw err
|
|
162
|
+
}
|
|
163
|
+
const r = await execPgsql(config, apikey, 'SELECT 1 AS ok')
|
|
164
|
+
if (!r.ok) {
|
|
165
|
+
console.error(`error: 连接测试失败 (${r.message})`)
|
|
166
|
+
return 1
|
|
167
|
+
}
|
|
168
|
+
console.log('连接测试通过(SELECT 1)')
|
|
169
|
+
return 0
|
|
170
|
+
}
|
|
171
|
+
|
|
153
172
|
if (sub === 'export') {
|
|
154
173
|
const config = loadConfig()
|
|
155
174
|
if (!config.envId || !config.apiKey) {
|
|
@@ -183,7 +202,13 @@ async function cmdConfig(argv) {
|
|
|
183
202
|
}
|
|
184
203
|
|
|
185
204
|
if (sub === 'import') {
|
|
186
|
-
|
|
205
|
+
const { values: importValues, positionals: importPositionals } = parseArgs({
|
|
206
|
+
strict: false,
|
|
207
|
+
allowPositionals: true,
|
|
208
|
+
args: argv,
|
|
209
|
+
options: { pass: { type: 'string' }, json: { type: 'boolean', short: 'j' } },
|
|
210
|
+
})
|
|
211
|
+
let body = importPositionals[1]
|
|
187
212
|
if (!body && !process.stdin.isTTY) body = await readStdinText()
|
|
188
213
|
if (!body) {
|
|
189
214
|
const err = new Error('config import 需要 <配置串>(位置参数或 stdin)')
|
|
@@ -193,10 +218,10 @@ async function cmdConfig(argv) {
|
|
|
193
218
|
const decoded = decodeConfigString(body)
|
|
194
219
|
let cfg = decoded.config
|
|
195
220
|
if (decoded.encrypted) {
|
|
196
|
-
let passphrase = process.env.ACT01_EXPORT_PASS
|
|
221
|
+
let passphrase = str(importValues, 'pass') ?? process.env.ACT01_EXPORT_PASS
|
|
197
222
|
if (!passphrase) {
|
|
198
223
|
if (!process.stdin.isTTY) {
|
|
199
|
-
const err = new Error('
|
|
224
|
+
const err = new Error('加密串需要口令:用 --pass <口令> 或 ACT01_EXPORT_PASS 传入')
|
|
200
225
|
err.exitCode = 2
|
|
201
226
|
throw err
|
|
202
227
|
}
|
|
@@ -311,6 +336,15 @@ function normalizeFromValues(values) {
|
|
|
311
336
|
|
|
312
337
|
/* ---------- skill ---------- */
|
|
313
338
|
|
|
339
|
+
/** 把 SKILL.md 装到 <root>/<SKILL_NAME>/SKILL.md(目录式,匹配主流 agent 技能布局) */
|
|
340
|
+
function installSkill(root) {
|
|
341
|
+
const dir = join(expandPath(root), SKILL_NAME)
|
|
342
|
+
mkdirSync(dir, { recursive: true, mode: 0o755 })
|
|
343
|
+
const file = join(dir, 'SKILL.md')
|
|
344
|
+
writeFileSync(file, SKILL_MD)
|
|
345
|
+
return file
|
|
346
|
+
}
|
|
347
|
+
|
|
314
348
|
async function cmdSkill(argv) {
|
|
315
349
|
const { values, positionals } = parseArgs({
|
|
316
350
|
strict: false,
|
|
@@ -326,13 +360,9 @@ async function cmdSkill(argv) {
|
|
|
326
360
|
}
|
|
327
361
|
|
|
328
362
|
if (values.install) {
|
|
329
|
-
const
|
|
330
|
-
const dir = expandPath(values.install)
|
|
331
|
-
mkdirSync(dir, { recursive: true, mode: 0o755 })
|
|
332
|
-
const file = join(dir, 'SKILL.md')
|
|
333
|
-
writeFileSync(file, SKILL_MD)
|
|
363
|
+
const file = installSkill(values.install)
|
|
334
364
|
console.log(`SKILL.md 已写入: ${file}`)
|
|
335
|
-
console.error(
|
|
365
|
+
console.error(`hint: 已装成 <技能根目录>/${SKILL_NAME}/SKILL.md;根目录指你的 agent 扫描的那个 skills 目录`)
|
|
336
366
|
return 0
|
|
337
367
|
}
|
|
338
368
|
|
|
@@ -340,6 +370,72 @@ async function cmdSkill(argv) {
|
|
|
340
370
|
return 0
|
|
341
371
|
}
|
|
342
372
|
|
|
373
|
+
/* ---------- setup(一条命令:装技能 + 导配置 + 查余额) ---------- */
|
|
374
|
+
|
|
375
|
+
/** 解析配置串(明文/加密)、写盘;返回 { envId, apiKey } */
|
|
376
|
+
function importConfig(body, passFlag) {
|
|
377
|
+
const decoded = decodeConfigString(body)
|
|
378
|
+
let cfg = decoded.config
|
|
379
|
+
if (decoded.encrypted) {
|
|
380
|
+
const pass = passFlag ?? process.env.ACT01_EXPORT_PASS
|
|
381
|
+
if (!pass) {
|
|
382
|
+
const err = new Error('加密串需要口令:用 ACT01_EXPORT_PASS 环境变量或 --pass 传入')
|
|
383
|
+
err.exitCode = 2
|
|
384
|
+
throw err
|
|
385
|
+
}
|
|
386
|
+
cfg = decoded.decode(pass)
|
|
387
|
+
}
|
|
388
|
+
if (!cfg.envId || !cfg.apiKey) {
|
|
389
|
+
const err = new Error('配置串内容不完整(缺 envId 或 apiKey)')
|
|
390
|
+
err.exitCode = 2
|
|
391
|
+
throw err
|
|
392
|
+
}
|
|
393
|
+
saveConfig({ envId: cfg.envId, apiKey: cfg.apiKey })
|
|
394
|
+
return cfg
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
async function cmdSetup(argv) {
|
|
398
|
+
const { values, positionals } = parseArgs({
|
|
399
|
+
strict: false,
|
|
400
|
+
allowPositionals: true,
|
|
401
|
+
args: argv,
|
|
402
|
+
options: { install: { type: 'string' }, pass: { type: 'string' }, json: { type: 'boolean', short: 'j' } },
|
|
403
|
+
})
|
|
404
|
+
|
|
405
|
+
let body = positionals[0] ?? process.env.ACT01_IMPORT_STRING
|
|
406
|
+
if (!body && !process.stdin.isTTY) body = await readStdinText()
|
|
407
|
+
if (!body) {
|
|
408
|
+
const err = new Error('setup 需要 <配置串>(位置参数 / stdin / ACT01_IMPORT_STRING)')
|
|
409
|
+
err.exitCode = 2
|
|
410
|
+
throw err
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
const cfg = importConfig(body, str(values, 'pass'))
|
|
414
|
+
const skillPath = values.install ? installSkill(values.install) : null
|
|
415
|
+
|
|
416
|
+
const account = await execPgsql(cfg, resolveApikey(cfg, undefined), ACCOUNT_SQL)
|
|
417
|
+
const wallet =
|
|
418
|
+
account.ok && account.rows?.[0]?.data?.wallet && typeof account.rows[0].data.wallet === 'object'
|
|
419
|
+
? account.rows[0].data.wallet
|
|
420
|
+
: {}
|
|
421
|
+
const points = account.ok ? Number(wallet.points || 0) : null
|
|
422
|
+
const label = account.ok ? String(wallet.label || 'account') : null
|
|
423
|
+
|
|
424
|
+
if (values.json === true) {
|
|
425
|
+
printJson({ ok: account.ok, envId: cfg.envId, apiKeySet: true, skillPath, balance: points, account: label, error: account.ok ? null : account.message })
|
|
426
|
+
} else {
|
|
427
|
+
console.log('setup 完成:')
|
|
428
|
+
console.log(skillPath ? ` 技能: SKILL.md → ${skillPath}` : ' 技能: (未装;加 --install <技能根目录>)')
|
|
429
|
+
console.log(` 配置: envId ${cfg.envId},apiKey 已打码写入 0600`)
|
|
430
|
+
if (account.ok) console.log(` 余额: ${points}(${label})`)
|
|
431
|
+
else {
|
|
432
|
+
console.error(` 余额: 查询失败 (${account.message})`)
|
|
433
|
+
return 1
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
return 0
|
|
437
|
+
}
|
|
438
|
+
|
|
343
439
|
/* ---------- redpacket check(零副作用参数校验) ---------- */
|
|
344
440
|
|
|
345
441
|
async function cmdCheck(argv) {
|
|
@@ -521,6 +617,7 @@ export async function main(argv) {
|
|
|
521
617
|
if (cmd === 'balance') return await cmdBalance(argv.slice(1))
|
|
522
618
|
if (cmd === 'mcp') return await startMcp()
|
|
523
619
|
if (cmd === 'skill') return await cmdSkill(argv.slice(1))
|
|
620
|
+
if (cmd === 'setup') return await cmdSetup(argv.slice(1))
|
|
524
621
|
if (cmd === 'redpacket') {
|
|
525
622
|
const sub = argv[1]
|
|
526
623
|
if (sub === 'create') return await cmdCreate(argv.slice(2))
|
package/src/mcp.js
CHANGED
|
@@ -11,11 +11,12 @@
|
|
|
11
11
|
* 纪律: MCP 模式下 stdout 只输出 JSON-RPC 报文,任何日志走 stderr。
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
+
import { VERSION } from './version.js'
|
|
14
15
|
import { loadConfig } from './store.js'
|
|
15
16
|
import { resolveApikey, execPgsql, gatewayBase } from './tcb.js'
|
|
16
17
|
import { normalizeCreateRedpacket, buildCreateSql } from './redpacket.js'
|
|
17
18
|
|
|
18
|
-
const SERVER_INFO = { name: 'act-01', version:
|
|
19
|
+
const SERVER_INFO = { name: 'act-01', version: VERSION }
|
|
19
20
|
const SUPPORTED = ['2025-11-25', '2025-06-18', '2025-03-26', '2024-11-05', '2024-10-07']
|
|
20
21
|
const LATEST = '2025-11-25'
|
|
21
22
|
|
package/src/redpacket.js
CHANGED
|
@@ -23,14 +23,14 @@ function optionalText(value, label, maxLen) {
|
|
|
23
23
|
export function normalizeCreateRedpacket(body) {
|
|
24
24
|
const mode = String(body.mode ?? '')
|
|
25
25
|
if (mode !== 'random' && mode !== 'equal')
|
|
26
|
-
throw new ValidationError('
|
|
26
|
+
throw new ValidationError('红包类型必须是 random 或 equal')
|
|
27
27
|
|
|
28
28
|
const totalPoints = positiveInteger(body.total_points, '总金额')
|
|
29
29
|
const count = positiveInteger(body.count, '份数')
|
|
30
30
|
const perUserLimit = positiveInteger(body.per_user_limit, '每人领取次数')
|
|
31
|
-
if (perUserLimit > count) throw new ValidationError('
|
|
31
|
+
if (perUserLimit > count) throw new ValidationError('每人领取次数不能超过份数')
|
|
32
32
|
if (mode === 'equal' && totalPoints < count)
|
|
33
|
-
throw new ValidationError('
|
|
33
|
+
throw new ValidationError('均分总额不能小于份数')
|
|
34
34
|
|
|
35
35
|
const shareTitle = optionalText(body.share_title, '分享标题', 60)
|
|
36
36
|
const shareCover = optionalText(body.share_cover, '分享封面', 500)
|
|
@@ -58,9 +58,9 @@ export function normalizeCreateRedpacket(body) {
|
|
|
58
58
|
const maxPoints = positiveInteger(body.max_points, '单份最大金额')
|
|
59
59
|
if (maxPoints < minPoints) throw new ValidationError('单份最大金额不能小于最小金额')
|
|
60
60
|
if (totalPoints < minPoints * count || totalPoints > maxPoints * count)
|
|
61
|
-
throw new ValidationError('
|
|
61
|
+
throw new ValidationError('总额不在可分配区间内(需介于 单份最小×份数 ~ 单份最大×份数)')
|
|
62
62
|
if (totalPoints % count !== 0)
|
|
63
|
-
throw new ValidationError('
|
|
63
|
+
throw new ValidationError('总额需能被份数整除(random 模式)')
|
|
64
64
|
|
|
65
65
|
return { mode, totalPoints, count, perUserLimit, minPoints, maxPoints, shareTitle, shareCover, note, requireGroup }
|
|
66
66
|
}
|
package/src/skill.js
CHANGED
|
@@ -23,6 +23,11 @@ const BODY = [
|
|
|
23
23
|
'',
|
|
24
24
|
'所有命令零依赖,Node >= 18。',
|
|
25
25
|
'',
|
|
26
|
+
'## 一条命令装好(推荐)',
|
|
27
|
+
'',
|
|
28
|
+
B + 'act-01 setup "<配置串>" --install <技能根目录>' + B + ' —— 一次完成:装技能 + 导配置 + 查余额,',
|
|
29
|
+
'口令走 ' + B + 'ACT01_EXPORT_PASS' + B + ' 环境变量;配置串也可走 stdin 或 ' + B + 'ACT01_IMPORT_STRING' + B + '。',
|
|
30
|
+
'',
|
|
26
31
|
'## 工具面',
|
|
27
32
|
'',
|
|
28
33
|
'| 途径 | 命令 / MCP 工具 | 副作用 |',
|
|
@@ -54,19 +59,21 @@ const BODY = [
|
|
|
54
59
|
'## Agent 行为规则',
|
|
55
60
|
'',
|
|
56
61
|
'1. **先 check 后 create**:参数没把握时,先调 redpacket_check,拿到校验报告再执行',
|
|
57
|
-
'2. **create 前必须向用户确认参数**(mode/total/count/区间/群归属)
|
|
62
|
+
'2. **create 前必须向用户确认参数**(mode/total/count/区间/群归属);确认后执行时,**非交互(非 TTY)必须带 --yes**,否则 CLI 会拒绝(MCP 客户端则弹审批)',
|
|
58
63
|
'3. **结果转述只给摘要**:packet_no、余额变化;不要猜测或编造未返回的字段',
|
|
59
64
|
'4. **失败处理**:错误信息是人话(余额不足/区间错误/整除错误),原样转告用户并给出修正建议;不要重试同参数的 create',
|
|
60
65
|
'5. **金额单位**:内部整数单位,不要做 ×100 或 ÷100 的换算,除非用户明确要求',
|
|
61
66
|
'',
|
|
62
67
|
'## 失败速查',
|
|
63
68
|
'',
|
|
64
|
-
'|
|
|
69
|
+
'| 错误里含 | 含义 | 修正 |',
|
|
65
70
|
'|---|---|---|',
|
|
66
|
-
'|
|
|
67
|
-
'| 整除 | total % count ≠ 0(random) | 调整 total 或 count |',
|
|
71
|
+
'| 必须是正整数 | 参数 ≤0 或非整数 | 传正整数(内部积分单位,不换算) |',
|
|
68
72
|
'| 不能超过份数 | per_user_limit > count | 降低 per_user_limit |',
|
|
69
|
-
'|
|
|
73
|
+
'| 不能小于份数 | equal 模式 total < count | 提高 total |',
|
|
74
|
+
'| 可分配区间 | total 不在 min×count ~ max×count | 调 min/max 或 total |',
|
|
75
|
+
'| 整除 | random 模式 total % count ≠ 0 | 调 total 或 count |',
|
|
76
|
+
'| 无法识别的配置串 | 配置串前缀/内容不对 | 用配套网页重新生成配置串 |',
|
|
70
77
|
'| 余额不足 / insufficient | 账户余额 < total | 先充值或调低 total |',
|
|
71
78
|
'',
|
|
72
79
|
'## 配置前置',
|