@blod-nm/1kuan-act-01-cli 0.1.4 → 0.1.5
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 +1 -1
- package/package.json +2 -3
- package/src/skill.js +14 -3
- package/src/version.js +1 -1
- package/config-helper.html +0 -220
package/README.md
CHANGED
|
@@ -22,7 +22,7 @@ act-01 config test
|
|
|
22
22
|
|
|
23
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
24
|
|
|
25
|
-
No local config?
|
|
25
|
+
No local config? Ask the team for a `config-helper.html` (a single local HTML page) to generate a transfer string, then:
|
|
26
26
|
|
|
27
27
|
```bash
|
|
28
28
|
act-01 config import "act01w.v1.xxxx" # prompts for the passphrase used at generation
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@blod-nm/1kuan-act-01-cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
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",
|
|
@@ -14,7 +14,6 @@
|
|
|
14
14
|
"files": [
|
|
15
15
|
"bin",
|
|
16
16
|
"src",
|
|
17
|
-
"config-helper.html",
|
|
18
17
|
"README.md"
|
|
19
18
|
],
|
|
20
19
|
"scripts": {
|
|
@@ -26,7 +25,7 @@
|
|
|
26
25
|
"build:all": "bun run build:js && bun run build:bin:all"
|
|
27
26
|
},
|
|
28
27
|
"engines": {
|
|
29
|
-
"node": ">=18"
|
|
28
|
+
"node": ">=18.3"
|
|
30
29
|
},
|
|
31
30
|
"keywords": [
|
|
32
31
|
"cli",
|
package/src/skill.js
CHANGED
|
@@ -56,6 +56,17 @@ const BODY = [
|
|
|
56
56
|
'2. **确认卡**:执行前列出全部参数,等用户确认',
|
|
57
57
|
'3. **库函数原子扣减**:并发/余额竞争由数据库兜底,不会扣穿',
|
|
58
58
|
'',
|
|
59
|
+
'## 日常最快路径(一句大白话 → 一次确认 → 执行)',
|
|
60
|
+
'',
|
|
61
|
+
'1. ' + B + 'act-01 redpacket check <flags>' + B + ' 拿校验结果(零副作用)',
|
|
62
|
+
'2. 把校验结果逐项复述给用户,等确认',
|
|
63
|
+
'3. 确认后执行:' + B + 'act-01 redpacket create <flags> --yes' + B + '(非交互必须带 ' + B + '--yes' + B + ')',
|
|
64
|
+
'',
|
|
65
|
+
'余额为 0 属正常:create 会被预检拦下,先让用户到运营台充值。',
|
|
66
|
+
'',
|
|
67
|
+
'> 更轻的日常形态(可选):' + B + 'act-01 mcp' + B + ' 接入支持 MCP 的客户端后,参数规格在工具 schema 里,',
|
|
68
|
+
'> 不必读本文档;客户端审批弹窗即"确认卡"。',
|
|
69
|
+
'',
|
|
59
70
|
'## Agent 行为规则',
|
|
60
71
|
'',
|
|
61
72
|
'1. **先 check 后 create**:参数没把握时,先调 redpacket_check,拿到校验报告再执行',
|
|
@@ -78,10 +89,10 @@ const BODY = [
|
|
|
78
89
|
'',
|
|
79
90
|
'## 配置前置',
|
|
80
91
|
'',
|
|
81
|
-
'目标机器需要云开发配置(envId + apiKey)
|
|
82
|
-
'(
|
|
92
|
+
'目标机器需要云开发配置(envId + apiKey)。没有配置时,让用户给你一条加密配置串',
|
|
93
|
+
'(团队用生成页产出),一条命令装好:',
|
|
83
94
|
'',
|
|
84
|
-
B + 'act-01
|
|
95
|
+
B + 'ACT01_EXPORT_PASS="<口令>" act-01 setup --install <技能根目录> "<配置串>"' + B,
|
|
85
96
|
'',
|
|
86
97
|
].join('\n')
|
|
87
98
|
|
package/src/version.js
CHANGED
package/config-helper.html
DELETED
|
@@ -1,220 +0,0 @@
|
|
|
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 < 串文件</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>
|