@blod-nm/1kuan-act-01-cli 0.1.1 → 0.1.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blod-nm/1kuan-act-01-cli",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
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,12 +3,14 @@
3
3
  */
4
4
 
5
5
  import { parseArgs } from 'node:util'
6
- import { configPath, loadConfig, saveConfig, decodeConfigString, encryptConfig } from './store.js'
6
+ import { VERSION } from './version.js'
7
+ import { join } from 'node:path'
8
+ import { configPath, expandPath, loadConfig, saveConfig, decodeConfigString, encryptConfig } from './store.js'
7
9
  import { ACCOUNT_SQL, execPgsql, resolveApikey, gatewayBase } from './tcb.js'
8
10
  import { buildCreateSql, confirmSummary, normalizeCreateRedpacket } from './redpacket.js'
11
+ import { SKILL_MD, SKILL_NAME } from './skill.js'
9
12
  import { startMcp } from './mcp.js'
10
13
 
11
- const VERSION = '0.1.0'
12
14
 
13
15
  const USAGE = `act-01 —— operations CLI(invoke preset database functions)
14
16
 
@@ -20,8 +22,9 @@ const USAGE = `act-01 —— operations CLI(invoke preset database functions)
20
22
  config set apiKey 写入 API Key(值走 stdin;不放命令行)
21
23
  config test 连接测试(SELECT 1,验证 envId/apiKey)
22
24
  config export 导出加密配置串(口令交互输入或 ACT01_EXPORT_PASS)
23
- config import <串|--stdin> 导入配置串(自动识别 加密/明文/网页格式)
25
+ config import <串|--stdin> [--pass <口令>] 导入配置串(加密串需口令;自动识别 三种格式)
24
26
  mcp 启动 MCP 服务(stdio;接入 Claude 等 AI 客户端)
27
+ skill 输出 agent 技能文档(--install <dir> 写入)
25
28
  balance account balance
26
29
  redpacket create create a record(pre-flight balance check, atomic deduct)
27
30
  redpacket check validate params(side-effect free)
@@ -180,7 +183,13 @@ async function cmdConfig(argv) {
180
183
  }
181
184
 
182
185
  if (sub === 'import') {
183
- let body = positionals[1]
186
+ const { values: importValues, positionals: importPositionals } = parseArgs({
187
+ strict: false,
188
+ allowPositionals: true,
189
+ args: argv,
190
+ options: { pass: { type: 'string' }, json: { type: 'boolean', short: 'j' } },
191
+ })
192
+ let body = importPositionals[1]
184
193
  if (!body && !process.stdin.isTTY) body = await readStdinText()
185
194
  if (!body) {
186
195
  const err = new Error('config import 需要 <配置串>(位置参数或 stdin)')
@@ -190,10 +199,10 @@ async function cmdConfig(argv) {
190
199
  const decoded = decodeConfigString(body)
191
200
  let cfg = decoded.config
192
201
  if (decoded.encrypted) {
193
- let passphrase = process.env.ACT01_EXPORT_PASS
202
+ let passphrase = str(importValues, 'pass') ?? process.env.ACT01_EXPORT_PASS
194
203
  if (!passphrase) {
195
204
  if (!process.stdin.isTTY) {
196
- const err = new Error('加密串需要口令:脚本里用 ACT01_EXPORT_PASS 传入')
205
+ const err = new Error('加密串需要口令:用 --pass <口令> 或 ACT01_EXPORT_PASS 传入')
197
206
  err.exitCode = 2
198
207
  throw err
199
208
  }
@@ -306,6 +315,37 @@ function normalizeFromValues(values) {
306
315
  })
307
316
  }
308
317
 
318
+ /* ---------- skill ---------- */
319
+
320
+ async function cmdSkill(argv) {
321
+ const { values, positionals } = parseArgs({
322
+ strict: false,
323
+ allowPositionals: true,
324
+ args: argv,
325
+ options: { install: { type: 'string' } },
326
+ })
327
+ const sub = positionals[0] ?? SKILL_NAME
328
+ if (sub !== SKILL_NAME) {
329
+ const err = new Error(`未知 skill: ${sub}(可用: ${SKILL_NAME})`)
330
+ err.exitCode = 2
331
+ throw err
332
+ }
333
+
334
+ if (values.install) {
335
+ const { mkdirSync, writeFileSync } = await import('node:fs')
336
+ const dir = expandPath(values.install)
337
+ mkdirSync(dir, { recursive: true, mode: 0o755 })
338
+ const file = join(dir, 'SKILL.md')
339
+ writeFileSync(file, SKILL_MD)
340
+ console.log(`SKILL.md 已写入: ${file}`)
341
+ console.error('hint: 把该目录加入你的 agent 技能路径')
342
+ return 0
343
+ }
344
+
345
+ console.log(SKILL_MD)
346
+ return 0
347
+ }
348
+
309
349
  /* ---------- redpacket check(零副作用参数校验) ---------- */
310
350
 
311
351
  async function cmdCheck(argv) {
@@ -486,6 +526,7 @@ export async function main(argv) {
486
526
  if (cmd === 'config') return await cmdConfig(argv.slice(1))
487
527
  if (cmd === 'balance') return await cmdBalance(argv.slice(1))
488
528
  if (cmd === 'mcp') return await startMcp()
529
+ if (cmd === 'skill') return await cmdSkill(argv.slice(1))
489
530
  if (cmd === 'redpacket') {
490
531
  const sub = argv[1]
491
532
  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 ADDED
@@ -0,0 +1,85 @@
1
+ /**
2
+ * 内置 agent skill:给 AI 客户端的完整操作技能文档。
3
+ * act-01 skill → 打印 SKILL.md(agent 抓 stdout 即可)
4
+ * act-01 skill --install <dir> → 写入 <dir>/SKILL.md
5
+ */
6
+
7
+ const B = '`' // 模板串内的反引号统一用变量拼接,避免转义地狱
8
+
9
+ const BODY = [
10
+ '---',
11
+ 'name: act-01-ops',
12
+ 'description: act-01 运营操作技能——通过 act-01 CLI / MCP 工具安全地创建 packet、查询余额。资金操作,遵守三道闸门。',
13
+ '---',
14
+ '',
15
+ '# act-01 运营操作',
16
+ '',
17
+ '通过云开发网关调用预置数据库函数(原子操作)。',
18
+ '',
19
+ '## 运行方式(二选一)',
20
+ '',
21
+ '- 已全局安装:' + B + 'act-01 <命令>' + B,
22
+ '- 未安装:每条命令前加 ' + B + 'npx -y @blod-nm/1kuan-act-01-cli' + B + ',如 ' + B + 'npx -y @blod-nm/1kuan-act-01-cli balance' + B,
23
+ '',
24
+ '所有命令零依赖,Node >= 18。',
25
+ '',
26
+ '## 工具面',
27
+ '',
28
+ '| 途径 | 命令 / MCP 工具 | 副作用 |',
29
+ '|---|---|---|',
30
+ '| 参数校验 | ' + B + 'act-01 redpacket check <flags>' + B + ' / ' + B + 'redpacket_check' + B + ' | 零副作用,优先用 |',
31
+ '| 余额查询 | ' + B + 'act-01 balance' + B + ' / ' + B + 'balance' + B + ' | 只读 |',
32
+ '| 创建 packet | ' + B + 'act-01 redpacket create <flags>' + B + ' / ' + B + 'redpacket_create' + B + ' | **有副作用:账户原子扣减** |',
33
+ '',
34
+ '## create 参数规格',
35
+ '',
36
+ '| 参数 | 规则 |',
37
+ '|---|---|',
38
+ '| mode | random(随机)/ equal(均分) |',
39
+ '| total_points | 正整数(内部单位,不换算) |',
40
+ '| count | 正整数(份数) |',
41
+ '| per_user_limit | 正整数,≤ count |',
42
+ '| min_points / max_points(仅 random) | max ≥ min;total ∈ [min×count, max×count];total % count === 0 |',
43
+ '| share_title / note | 可选,≤60 / ≤20 字 |',
44
+ '| require_group | 布尔,默认 false |',
45
+ '',
46
+ 'equal 模式自动 min = floor(total/count),max = ceil(total/count)。',
47
+ '',
48
+ '## 安全三道闸门',
49
+ '',
50
+ '1. **CLI 预检**:balance < total_points 时直接拒绝(不触数据库写)',
51
+ '2. **确认卡**:执行前列出全部参数,等用户确认',
52
+ '3. **库函数原子扣减**:并发/余额竞争由数据库兜底,不会扣穿',
53
+ '',
54
+ '## Agent 行为规则',
55
+ '',
56
+ '1. **先 check 后 create**:参数没把握时,先调 redpacket_check,拿到校验报告再执行',
57
+ '2. **create 前必须向用户确认参数**(mode/total/count/区间/群归属),MCP 客户端会弹审批',
58
+ '3. **结果转述只给摘要**:packet_no、余额变化;不要猜测或编造未返回的字段',
59
+ '4. **失败处理**:错误信息是人话(余额不足/区间错误/整除错误),原样转告用户并给出修正建议;不要重试同参数的 create',
60
+ '5. **金额单位**:内部整数单位,不要做 ×100 或 ÷100 的换算,除非用户明确要求',
61
+ '',
62
+ '## 失败速查',
63
+ '',
64
+ '| 错误里含 | 含义 | 修正 |',
65
+ '|---|---|---|',
66
+ '| 必须是正整数 | 参数 ≤0 或非整数 | 传正整数(内部积分单位,不换算) |',
67
+ '| 不能超过份数 | per_user_limit > count | 降低 per_user_limit |',
68
+ '| 不能小于份数 | equal 模式 total < count | 提高 total |',
69
+ '| 可分配区间 | total 不在 min×count ~ max×count | 调 min/max 或 total |',
70
+ '| 整除 | random 模式 total % count ≠ 0 | 调 total 或 count |',
71
+ '| 无法识别的配置串 | 配置串前缀/内容不对 | 用配套网页重新生成配置串 |',
72
+ '| 余额不足 / insufficient | 账户余额 < total | 先充值或调低 total |',
73
+ '',
74
+ '## 配置前置',
75
+ '',
76
+ '目标机器需要云开发配置(envId + apiKey)。没有配置文件时,让用户用配套网页',
77
+ '(config-helper.html)生成加密配置串,然后:',
78
+ '',
79
+ B + 'act-01 config import "配置串"' + B + '(会提示输入生成时的口令)',
80
+ '',
81
+ ].join('\n')
82
+
83
+ export const SKILL_MD = BODY
84
+
85
+ export const SKILL_NAME = 'act-01-ops'
package/src/tcb.js CHANGED
@@ -3,13 +3,15 @@
3
3
  *
4
4
  * 端点:POST https://{envId}.api.tcloudbasegateway.com/v1/rdb/exec-pgsql
5
5
  * 鉴权:Authorization: Bearer <apiKey>
6
- * 请求体:{ sql } —— exec-pgsql 只收一整条 SQL(无 parameters 通道),
6
+ * 请求体:{ sql, role } —— exec-pgsql 只收一整条 SQL(无 parameters 通道),
7
7
  * $1..$11 由客户端绑定成带类型的字面量(文本单引号翻倍,数值/布尔直书)。
8
+ * 角色:固定 cloudbase_postgres(管理可读写)——网关默认是只读,不显式升权写语句会被 42501 拒。
8
9
  */
9
10
 
10
11
  import { loadConfig } from './store.js'
11
12
 
12
13
  export const GATEWAY_DOMAIN = 'api.tcloudbasegateway.com'
14
+ export const PG_ROLE = 'cloudbase_postgres'
13
15
 
14
16
  export function resolveApikey(config, flagValue) {
15
17
  const raw = flagValue ?? process.env.ACT01_API_KEY ?? config.apiKey
@@ -44,7 +46,7 @@ export async function execPgsql(config, apikey, sql) {
44
46
  'Content-Type': 'application/json',
45
47
  Authorization: `Bearer ${apikey}`,
46
48
  },
47
- body: JSON.stringify({ sql }),
49
+ body: JSON.stringify({ sql, role: PG_ROLE }),
48
50
  })
49
51
  } catch (err) {
50
52
  throw new Error(`网关连接失败:${err instanceof Error ? err.message : String(err)}`)
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.3'