@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 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 create --mode random --total 10000 --count 50 --per-user 2 --min 100 --max 500
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blod-nm/1kuan-act-01-cli",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "type": "module",
5
5
  "description": "Internal operations CLI: invoke preset database functions via a gateway. Zero dependencies, MCP (stdio) support.",
6
6
  "license": "MIT",
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 <dir> 写入)
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
- let body = positionals[1]
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('加密串需要口令:脚本里用 ACT01_EXPORT_PASS 传入')
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 { mkdirSync, writeFileSync } = await import('node:fs')
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('hint: 把该目录加入你的 agent 技能路径')
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: '0.1.0' }
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('type must be random or equal')
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('per-user limit cannot exceed count')
31
+ if (perUserLimit > count) throw new ValidationError('每人领取次数不能超过份数')
32
32
  if (mode === 'equal' && totalPoints < count)
33
- throw new ValidationError('total must be at least count')
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('total is outside the min..max × count range')
61
+ throw new ValidationError('总额不在可分配区间内(需介于 单份最小×份数 ~ 单份最大×份数)')
62
62
  if (totalPoints % count !== 0)
63
- throw new ValidationError('total must be divisible by count')
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/区间/群归属),MCP 客户端会弹审批',
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
- '| 可分配区间 | total 不在 min×count ~ max×count | 调整 min/max 或 total |',
67
- '| 整除 | total % count ≠ 0(random) | 调整 total 或 count |',
71
+ '| 必须是正整数 | 参数 ≤0 或非整数 | 传正整数(内部积分单位,不换算) |',
68
72
  '| 不能超过份数 | per_user_limit > count | 降低 per_user_limit |',
69
- '| at least count | equal 模式 total < count | 提高 total |',
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
  '## 配置前置',
package/src/version.js ADDED
@@ -0,0 +1,6 @@
1
+ /**
2
+ * 版本号单一来源。main.js / mcp.js 共用,发布时只改这里 + package.json
3
+ * (test/version.test.js 会断言两者一致,防止再漏升)。
4
+ */
5
+
6
+ export const VERSION = '0.1.4'