@blod-nm/1kuan-act-01-cli 0.0.0-stage → 0.1.0

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
@@ -1,3 +1,69 @@
1
- # Temporary Holding Version
1
+ # @blod-nm/1kuan-act-01-cli
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Internal operations CLI: invoke preset database functions through a gateway. Zero dependencies, MCP (stdio) support, single-file executable.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm i -g @blod-nm/1kuan-act-01-cli
9
+ # or run without installing
10
+ npx @blod-nm/1kuan-act-01-cli --help
11
+ ```
12
+
13
+ Requires Node >= 18. Zero runtime dependencies.
14
+
15
+ ## Configuration
16
+
17
+ ```bash
18
+ act-01 config set envId <env-id>
19
+ echo "secret" | act-01 config set apiKey --stdin
20
+ act-01 config test
21
+ ```
22
+
23
+ Stored at `~/.config/act-01/config.json` (0600, atomic write). `apiKey` can also come from the `ACT01_API_KEY` environment variable. Never pass it as a CLI argument.
24
+
25
+ No local config? Generate a transfer string with the bundled [config helper](./config-helper.html) (runs locally in the browser), then:
26
+
27
+ ```bash
28
+ act-01 config import "act01w.v1.xxxx" # prompts for the passphrase used at generation
29
+ ```
30
+
31
+ Transfer strings are AES-256-GCM encrypted; the passphrase never travels with the string.
32
+
33
+ ## Operations
34
+
35
+ ```bash
36
+ act-01 create --mode random --total 10000 --count 50 --per-user 2 --min 100 --max 500
37
+ ```
38
+
39
+ - Amounts are integer units, no conversion
40
+ - Pre-flight balance check runs before execution; insufficient balance is rejected locally
41
+ - Interactive confirm card lists every parameter before the atomic database function call
42
+ - `--dry-run` prints the function call without executing; `--json` for machine output
43
+
44
+ ## MCP (AI client integration)
45
+
46
+ ```bash
47
+ act-01 mcp
48
+ # register with a client, e.g.
49
+ claude mcp add act-01 -- act-01 mcp
50
+ ```
51
+
52
+ Exposes tools for the operations above. Side-effecting tools are subject to client-side approval; secrets never enter the tool context.
53
+
54
+ ## Security model
55
+
56
+ - The API key equals database admin: stored 0600, env var / stdin / encrypted transfer string only — never as a CLI argument
57
+ - Record lifecycle is TTL-driven; global invalidation = rotate the credentials
58
+ - Failed pre-flight or database checks exit non-zero with a human-readable reason and hint
59
+
60
+ ## Package an executable (optional)
61
+
62
+ ```bash
63
+ bun run build:bin # single-file for current platform
64
+ bun run build:bin:all # linux/darwin/windows
65
+ ```
66
+
67
+ ## License
68
+
69
+ MIT
package/bin/act-01.js ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env node
2
+ import { main } from '../src/main.js'
3
+
4
+ // 标记"真命令行直跑":向导式补齐只在这种场景出现
5
+ // (测试/程序内调用 main() 时不设,缺参一律走确定性报错)
6
+ process.env.ACT01_CLI_INVOKED = '1'
7
+
8
+ process.exit(await main(process.argv.slice(2)))
@@ -0,0 +1,220 @@
1
+ <!doctype html>
2
+ <html lang="zh-CN">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title>act-01 配置串生成器</title>
7
+ <style>
8
+ :root { color-scheme: light dark; }
9
+ * { box-sizing: border-box; }
10
+ body {
11
+ margin: 0; padding: 24px;
12
+ font: 15px/1.65 -apple-system, 'PingFang SC', 'Microsoft YaHei', system-ui, sans-serif;
13
+ background: light-dark(#f6f4ef, #17151f); color: light-dark(#262233, #e8e5f0);
14
+ font-variant-numeric: tabular-nums; -webkit-font-smoothing: antialiased;
15
+ }
16
+ .wrap { max-width: 640px; margin: 0 auto; display: grid; gap: 20px; }
17
+ h1 { font-size: 20px; margin: 0 0 4px; letter-spacing: -0.01em; }
18
+ .sub { color: light-dark(#7a7490, #9a94b0); font-size: 13px; margin: 0; }
19
+ .card {
20
+ border: 2px solid light-dark(#413a5e, #cbc5e0); border-radius: 16px;
21
+ padding: 20px; background: light-dark(#fffdf8, #201d2b);
22
+ box-shadow: 5px 5px 0 light-dark(#413a5e, #0c0a12);
23
+ }
24
+ .card h2 { font-size: 15px; margin: 0 0 12px; }
25
+ label { display: block; font-size: 13px; font-weight: 600; margin: 0 0 4px; }
26
+ label .opt { font-weight: 400; opacity: .65; }
27
+ input[type=text], input[type=password] {
28
+ width: 100%; padding: 9px 12px; font: inherit; font-size: 14px;
29
+ border: 2px solid light-dark(#413a5e, #cbc5e0); border-radius: 10px;
30
+ background: light-dark(#fff, #262233); color: inherit; margin-bottom: 12px;
31
+ }
32
+ input:focus { outline: 2px solid light-dark(#7c5cff, #a78bfa); outline-offset: 1px; }
33
+ button {
34
+ padding: 9px 18px; font: inherit; font-size: 14px; font-weight: 600;
35
+ border: 2px solid light-dark(#413a5e, #cbc5e0); border-radius: 999px;
36
+ background: light-dark(#7c5cff, #a78bfa); color: light-dark(#fff, #1a1526);
37
+ box-shadow: 3px 3px 0 light-dark(#413a5e, #0c0a12); cursor: pointer;
38
+ transition: transform 120ms cubic-bezier(0.34,1.56,0.64,1), box-shadow 120ms;
39
+ }
40
+ button:active { transform: translate(2px,2px); box-shadow: 1px 1px 0 light-dark(#413a5e, #0c0a12); }
41
+ button.ghost { background: transparent; color: inherit; }
42
+ .out {
43
+ font-family: ui-monospace, 'SF Mono', Menlo, monospace; font-size: 12px;
44
+ word-break: break-all; padding: 10px 12px; border-radius: 10px;
45
+ background: light-dark(#fff, #262233);
46
+ border: 2px solid light-dark(#413a5e, #cbc5e0); min-height: 42px; white-space: pre-wrap;
47
+ }
48
+ .row { display: flex; gap: 10px; align-items: center; }
49
+ .row input { flex: 1; margin-bottom: 0; }
50
+ .hint { font-size: 12px; opacity: .65; margin: 6px 0 0; }
51
+ .err { color: light-dark(#c2334d, #ff8fa3); font-size: 13px; margin: 8px 0 0; }
52
+ .ok { color: light-dark(#0a7d55, #4ade80); font-size: 13px; margin: 8px 0 0; }
53
+ code {
54
+ font-family: ui-monospace, Menlo, monospace; font-size: 12px;
55
+ background: light-dark(#efece3, #262233); padding: 2px 6px; border-radius: 6px;
56
+ }
57
+ .toggle { display: flex; gap: 14px; font-size: 13px; margin-bottom: 12px; }
58
+ </style>
59
+ </head>
60
+ <body>
61
+ <div class="wrap">
62
+ <header>
63
+ <h1>🔑 act-01 配置串生成器</h1>
64
+ <p class="sub">填入云开发原始配置 → 生成加密传递串。全本地计算,无网络请求,无任何存储。</p>
65
+ </header>
66
+
67
+ <div class="card">
68
+ <h2>① 填入原始配置</h2>
69
+ <label>envId(环境 ID)</label>
70
+ <input id="envId" type="text" placeholder="cloud1-xxxxxx" autocomplete="off">
71
+
72
+ <label>apiKey(腾讯云开发 API Key,需管理员权限)</label>
73
+ <input id="apiKey" type="password" placeholder="act- 开头或平台管理生成" autocomplete="off">
74
+
75
+ <label>口令 <span class="opt">— 加密用,导入端必须输入同一个;不含在配置串里</span></label>
76
+ <div class="row">
77
+ <input id="pass" type="password" placeholder="自己定一个,别用弱口令" autocomplete="new-password">
78
+ <button class="ghost" id="genPass">随机生成</button>
79
+ </div>
80
+
81
+ <div class="toggle">
82
+ <label style="margin:0;font-weight:400"><input type="radio" name="mode" value="enc" checked> 加密串(需口令解密,推荐)</label>
83
+ <label style="margin:0;font-weight:400"><input type="radio" name="mode" value="plain"> 明文串(便捷,可读)</label>
84
+ </div>
85
+
86
+ <button id="gen">生成配置串</button>
87
+ <p class="err" id="err" hidden></p>
88
+ </div>
89
+
90
+ <div class="card" id="outCard" hidden>
91
+ <h2>② 配置串(复制发出去)</h2>
92
+ <div class="out" id="out"></div>
93
+ <div class="row" style="margin-top:10px">
94
+ <button id="copy">复制</button>
95
+ <span class="hint" style="align-self:center">目标机器执行:<code>act-01 config import --stdin &lt; 串文件</code>(口令会提示输入)</span>
96
+ </div>
97
+ </div>
98
+
99
+ <div class="card">
100
+ <h2>③ 校验(可选)</h2>
101
+ <label>粘贴配置串</label>
102
+ <input id="vStr" type="text" autocomplete="off" class="mono-out">
103
+ <label>口令</label>
104
+ <input id="vPass" type="password" autocomplete="off">
105
+ <button class="ghost" id="verify">解密校验</button>
106
+ <p class="err" id="vErr" hidden></p>
107
+ <p class="ok" id="vOk" hidden></p>
108
+ </div>
109
+ </div>
110
+
111
+ <script>
112
+ const $ = (id) => document.getElementById(id)
113
+ const enc = new TextEncoder()
114
+
115
+ function b64url(bytes) {
116
+ let bin = ''
117
+ for (const b of bytes) bin += String.fromCharCode(b)
118
+ return btoa(bin).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '')
119
+ }
120
+
121
+ function b64urlDecode(text) {
122
+ const bin = atob(text.replace(/-/g, '+').replace(/_/g, '/'))
123
+ return Uint8Array.from(bin, (c) => c.charCodeAt(0))
124
+ }
125
+
126
+ async function deriveKey(pass, salt) {
127
+ const km = await crypto.subtle.importKey('raw', enc.encode(pass), 'PBKDF2', false, ['deriveKey'])
128
+ return crypto.subtle.deriveKey(
129
+ { name: 'PBKDF2', salt, iterations: 310_000, hash: 'SHA-256' },
130
+ km, { name: 'AES-GCM', length: 256 }, false, ['encrypt', 'decrypt'],
131
+ )
132
+ }
133
+
134
+ async function encryptWeb(obj, pass) {
135
+ const salt = crypto.getRandomValues(new Uint8Array(16))
136
+ const iv = crypto.getRandomValues(new Uint8Array(12))
137
+ const key = await deriveKey(pass, salt)
138
+ const ct = new Uint8Array(await crypto.subtle.encrypt({ name: 'AES-GCM', iv }, key, enc.encode(JSON.stringify(obj))))
139
+ const packed = new Uint8Array(salt.length + iv.length + ct.length)
140
+ packed.set(salt, 0); packed.set(iv, salt.length); packed.set(ct, salt.length + iv.length)
141
+ return 'act01w.v1.' + b64url(packed)
142
+ }
143
+
144
+ async function decryptWeb(str, pass) {
145
+ const body = str.trim()
146
+ const data = b64urlDecode(body.slice(body.indexOf('.') + 1))
147
+ if (data.length < 44) throw new Error('配置串过短或已损坏')
148
+ const salt = data.slice(0, 16)
149
+ const iv = data.slice(16, 28)
150
+ const rest = data.slice(28)
151
+ const key = await deriveKey(pass, salt)
152
+ const pt = await crypto.subtle.decrypt({ name: 'AES-GCM', iv }, key, rest.slice(0, rest.length - 16))
153
+ return JSON.parse(new TextDecoder().decode(pt))
154
+ }
155
+
156
+ function collect() {
157
+ const envId = $('envId').value.trim()
158
+ const apiKey = $('apiKey').value.trim()
159
+ const pass = $('pass').value
160
+ if (!envId) throw new Error('envId 未填写')
161
+ if (!apiKey) throw new Error('apiKey 未填写')
162
+ if ($('pass').value && pass.length < 8) throw new Error('口令至少 8 位')
163
+ return { config: { envId, apiKey }, pass }
164
+ }
165
+
166
+ function showErr(id, msg) {
167
+ const el = $(id); el.hidden = !msg; el.textContent = msg || ''
168
+ }
169
+
170
+ $('genPass').addEventListener('click', () => {
171
+ const bytes = crypto.getRandomValues(new Uint8Array(18))
172
+ let bin = ''; for (const b of bytes) bin += String.fromCharCode(b)
173
+ $('pass').value = btoa(bin).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '')
174
+ })
175
+
176
+ $('gen').addEventListener('click', async () => {
177
+ showErr('err', '')
178
+ $('outCard').hidden = true
179
+ try {
180
+ const { config, pass } = collect()
181
+ const mode = document.querySelector('input[name=mode]:checked').value
182
+ let out
183
+ if (mode === 'enc') {
184
+ if (!pass) throw new Error('加密模式需要口令')
185
+ out = await encryptWeb(config, pass)
186
+ } else {
187
+ out = 'act01plain.' + b64url(enc.encode(JSON.stringify(config)))
188
+ }
189
+ $('out').textContent = out
190
+ $('outCard').hidden = false
191
+ } catch (e) {
192
+ showErr('err', e.message)
193
+ }
194
+ })
195
+
196
+ $('copy').addEventListener('click', async () => {
197
+ await navigator.clipboard.writeText($('out').textContent)
198
+ $('copy').textContent = '已复制 ✓'
199
+ setTimeout(() => ($('copy').textContent = '复制'), 1200)
200
+ })
201
+
202
+ $('verify').addEventListener('click', async () => {
203
+ showErr('vErr', ''); $('vOk').hidden = true
204
+ try {
205
+ const str = $('vStr').value.trim()
206
+ const pass = $('vPass').value
207
+ let cfg
208
+ if (str.startsWith('act01w.v1.')) cfg = await decryptWeb(str, pass)
209
+ else if (str.startsWith('act01.v1.')) throw new Error('这是 CLI scrypt 格式串,请用 act-01 CLI 解')
210
+ else if (str.startsWith('act01plain.')) cfg = JSON.parse(atob(str.slice('act01plain.'.length).replace(/-/g,'+').replace(/_/g,'/')))
211
+ else throw new Error('无法识别的串格式')
212
+ $('vOk').hidden = false
213
+ $('vOk').textContent = `✓ 解密成功:envId=${cfg.envId},apiKey=${cfg.apiKey.slice(0, 6)}…(不回显全文)`
214
+ } catch (e) {
215
+ showErr('vErr', e.message)
216
+ }
217
+ })
218
+ </script>
219
+ </body>
220
+ </html>
package/package.json CHANGED
@@ -1,6 +1,40 @@
1
1
  {
2
2
  "name": "@blod-nm/1kuan-act-01-cli",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "description": "Internal operations CLI: invoke preset database functions via a gateway. Zero dependencies, MCP (stdio) support.",
6
+ "license": "MIT",
7
+ "bin": {
8
+ "act-01": "bin/act-01.js"
9
+ },
10
+ "main": "./src/main.js",
11
+ "exports": {
12
+ ".": "./src/main.js"
13
+ },
14
+ "files": [
15
+ "bin",
16
+ "src",
17
+ "config-helper.html",
18
+ "README.md"
19
+ ],
20
+ "scripts": {
21
+ "test": "bun test test/",
22
+ "build:js": "bun build --target=node --minify bin/act-01.js --outfile dist/act-01.js",
23
+ "build:bin": "bun build --compile --minify bin/act-01.js --outfile dist/act-01",
24
+ "build:bin:all": "bun run build:bin -- --target=bun-linux-x64 --outfile dist/act-01-linux-x64 && bun run build:bin -- --target=bun-linux-arm64 --outfile dist/act-01-linux-arm64 && bun run build:bin -- --target=bun-darwin-arm64 --outfile dist/act-01-darwin-arm64 && bun run build:bin -- --target=bun-darwin-x64 --outfile dist/act-01-darwin-x64 && bun run build:bin -- --target=bun-windows-x64 --outfile dist/act-01-windows-x64.exe",
25
+ "build": "bun run build:js && bun run build:bin",
26
+ "build:all": "bun run build:js && bun run build:bin:all"
27
+ },
28
+ "engines": {
29
+ "node": ">=18"
30
+ },
31
+ "keywords": [
32
+ "cli",
33
+ "mcp",
34
+ "internal"
35
+ ],
36
+ "publishConfig": {
37
+ "access": "public",
38
+ "registry": "https://registry.npmjs.org"
39
+ }
40
+ }