@lingerai/cli 0.1.1 → 0.2.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/package.json +1 -1
- package/src/api.js +17 -0
- package/src/callback-pages.js +174 -0
- package/src/cli-parse.js +18 -4
- package/src/format.js +15 -1
- package/src/index.js +50 -5
- package/src/oauth.js +7 -4
package/package.json
CHANGED
package/src/api.js
CHANGED
|
@@ -160,6 +160,23 @@ export async function runtimeAct(baseUrl, token, action, payload, idempotencyKey
|
|
|
160
160
|
// 能力上架(独立端点 · 卖方)
|
|
161
161
|
// ============================================================
|
|
162
162
|
|
|
163
|
+
/**
|
|
164
|
+
* 上报技能清单(卖方·A 流程第一步)。
|
|
165
|
+
* 把 agent 自己会的技能写进平台 agents.skill_catalog,供网页「AI 识别」挑能力卡。
|
|
166
|
+
* 用 agent token 调用时后端从 token 推导 agent_id(agent 不必知道自己 UUID)。
|
|
167
|
+
* @param {Array<{name:string,description:string}>} skills 技能清单
|
|
168
|
+
*/
|
|
169
|
+
export async function reportSkills(baseUrl, token, skills, idempotencyKey, opts = {}) {
|
|
170
|
+
return authedPost(
|
|
171
|
+
baseUrl,
|
|
172
|
+
'/api/v1/capabilities/skill_catalog',
|
|
173
|
+
token,
|
|
174
|
+
{ skills },
|
|
175
|
+
idempotencyKey,
|
|
176
|
+
opts
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
|
|
163
180
|
/**
|
|
164
181
|
* 创建能力卡草稿(卖方)。
|
|
165
182
|
* @param {object} capData CreateCapabilityRequest 字段(agent_id + title 必填,其余草稿可空)
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
// -*- linger auth login 本地 loopback 回调页(三状态 HTML)-*-
|
|
2
|
+
//
|
|
3
|
+
// 设计:对齐 A2A 平台网页设计规范(a2a-frontend/src/shared/tokens.css)——
|
|
4
|
+
// 白底(#F5F6F8 / #FFFFFF)+ 近黑文字(#0F1419 / #4B5563)+ 黑白为主、克制。
|
|
5
|
+
// 不用桌面端 Aria 琥珀金(那是另一条产品线的品牌色 · 2026-06-20 产品负责人纠正)。
|
|
6
|
+
// 参考飞书/主流 OAuth 成功页:居中、一屏、纯内联零外部资源(localhost 离线可渲染)、
|
|
7
|
+
// 图标用内联 SVG、动画包 prefers-reduced-motion 降级、文案极简(每页 1 句 caption)。
|
|
8
|
+
//
|
|
9
|
+
// 三个常量对应 oauth.js loopback handler 的三个 res.end:
|
|
10
|
+
// SUCCESS — 拿到 code(绑定成功 · 带「打开 Linger 主页」按钮)
|
|
11
|
+
// ERROR — error(授权未完成 · 回命令行重试)
|
|
12
|
+
// MISSING — 缺授权码(回调异常)
|
|
13
|
+
|
|
14
|
+
const CALLBACK_HTML_SUCCESS = `<!DOCTYPE html>
|
|
15
|
+
<html lang="zh-CN">
|
|
16
|
+
<head>
|
|
17
|
+
<meta charset="utf-8">
|
|
18
|
+
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
|
|
19
|
+
<title>绑定成功 · Linger</title>
|
|
20
|
+
<style>
|
|
21
|
+
*{margin:0;padding:0;box-sizing:border-box}
|
|
22
|
+
html,body{height:100%}
|
|
23
|
+
body{
|
|
24
|
+
font-family:-apple-system,BlinkMacSystemFont,"SF Pro Display","SF Pro Text","Segoe UI","PingFang SC","Hiragino Sans GB","Microsoft YaHei",system-ui,sans-serif;
|
|
25
|
+
background:#F5F6F8;color:#0F1419;
|
|
26
|
+
display:flex;align-items:center;justify-content:center;
|
|
27
|
+
min-height:100vh;min-height:100dvh;
|
|
28
|
+
padding:24px;line-height:1.6;-webkit-font-smoothing:antialiased;
|
|
29
|
+
}
|
|
30
|
+
.card{
|
|
31
|
+
width:100%;max-width:380px;text-align:center;
|
|
32
|
+
padding:8px;opacity:0;transform:translateY(10px);
|
|
33
|
+
animation:rise .6s cubic-bezier(.16,1,.3,1) .05s forwards;
|
|
34
|
+
}
|
|
35
|
+
.badge{
|
|
36
|
+
width:72px;height:72px;border-radius:999px;margin:0 auto 32px;
|
|
37
|
+
display:flex;align-items:center;justify-content:center;
|
|
38
|
+
background:rgba(15,20,25,.06);
|
|
39
|
+
}
|
|
40
|
+
.badge svg{width:34px;height:34px}
|
|
41
|
+
.check{
|
|
42
|
+
fill:none;stroke:#0F1419;stroke-width:2.5;
|
|
43
|
+
stroke-linecap:round;stroke-linejoin:round;
|
|
44
|
+
stroke-dasharray:32;stroke-dashoffset:32;
|
|
45
|
+
animation:draw .5s cubic-bezier(.65,0,.45,1) .35s forwards;
|
|
46
|
+
}
|
|
47
|
+
h1{font-size:21px;font-weight:500;letter-spacing:.02em;margin-bottom:12px}
|
|
48
|
+
p{font-size:14px;color:#4B5563;letter-spacing:.02em;margin-bottom:36px}
|
|
49
|
+
.btn{
|
|
50
|
+
display:inline-block;padding:13px 32px;border-radius:8px;
|
|
51
|
+
background:#0F1419;color:#fff;
|
|
52
|
+
font-size:14px;font-weight:500;letter-spacing:.02em;text-decoration:none;
|
|
53
|
+
transition:transform .2s ease-out,background .2s ease-out;
|
|
54
|
+
}
|
|
55
|
+
.btn:hover{background:#2C3340}
|
|
56
|
+
.btn:active{transform:scale(.97)}
|
|
57
|
+
@keyframes rise{to{opacity:1;transform:translateY(0)}}
|
|
58
|
+
@keyframes draw{to{stroke-dashoffset:0}}
|
|
59
|
+
@media (prefers-reduced-motion:reduce){
|
|
60
|
+
.card{animation:none;opacity:1;transform:none}
|
|
61
|
+
.check{animation:none;stroke-dashoffset:0}
|
|
62
|
+
.btn{transition:none}
|
|
63
|
+
}
|
|
64
|
+
</style>
|
|
65
|
+
</head>
|
|
66
|
+
<body>
|
|
67
|
+
<main class="card">
|
|
68
|
+
<div class="badge" aria-hidden="true">
|
|
69
|
+
<svg viewBox="0 0 24 24"><path class="check" d="M5 13l4 4L19 7"/></svg>
|
|
70
|
+
</div>
|
|
71
|
+
<h1>绑定成功 · 已连接 Linger</h1>
|
|
72
|
+
<p>可以关闭此页面,回到命令行</p>
|
|
73
|
+
<a class="btn" href="https://a2a.linger.chimap.cn">打开 Linger 主页</a>
|
|
74
|
+
</main>
|
|
75
|
+
</body>
|
|
76
|
+
</html>`;
|
|
77
|
+
|
|
78
|
+
const CALLBACK_HTML_ERROR = `<!DOCTYPE html>
|
|
79
|
+
<html lang="zh-CN">
|
|
80
|
+
<head>
|
|
81
|
+
<meta charset="utf-8">
|
|
82
|
+
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
|
|
83
|
+
<title>授权未完成 · Linger</title>
|
|
84
|
+
<style>
|
|
85
|
+
*{margin:0;padding:0;box-sizing:border-box}
|
|
86
|
+
html,body{height:100%}
|
|
87
|
+
body{
|
|
88
|
+
font-family:-apple-system,BlinkMacSystemFont,"SF Pro Display","SF Pro Text","Segoe UI","PingFang SC","Hiragino Sans GB","Microsoft YaHei",system-ui,sans-serif;
|
|
89
|
+
background:#F5F6F8;color:#0F1419;
|
|
90
|
+
display:flex;align-items:center;justify-content:center;
|
|
91
|
+
min-height:100vh;min-height:100dvh;
|
|
92
|
+
padding:24px;line-height:1.6;-webkit-font-smoothing:antialiased;
|
|
93
|
+
}
|
|
94
|
+
.card{
|
|
95
|
+
width:100%;max-width:380px;text-align:center;padding:8px;
|
|
96
|
+
opacity:0;transform:translateY(10px);
|
|
97
|
+
animation:rise .6s cubic-bezier(.16,1,.3,1) .05s forwards;
|
|
98
|
+
}
|
|
99
|
+
.badge{
|
|
100
|
+
width:72px;height:72px;border-radius:999px;margin:0 auto 32px;
|
|
101
|
+
display:flex;align-items:center;justify-content:center;
|
|
102
|
+
background:rgba(15,20,25,.05);
|
|
103
|
+
}
|
|
104
|
+
.badge svg{width:32px;height:32px}
|
|
105
|
+
.mark{fill:none;stroke:#0F1419;stroke-width:2.2;stroke-linecap:round;opacity:.55}
|
|
106
|
+
h1{font-size:21px;font-weight:500;letter-spacing:.02em;margin-bottom:12px}
|
|
107
|
+
p{font-size:14px;color:#4B5563;letter-spacing:.02em}
|
|
108
|
+
@keyframes rise{to{opacity:1;transform:translateY(0)}}
|
|
109
|
+
@media (prefers-reduced-motion:reduce){.card{animation:none;opacity:1;transform:none}}
|
|
110
|
+
</style>
|
|
111
|
+
</head>
|
|
112
|
+
<body>
|
|
113
|
+
<main class="card">
|
|
114
|
+
<div class="badge" aria-hidden="true">
|
|
115
|
+
<svg viewBox="0 0 24 24">
|
|
116
|
+
<line class="mark" x1="12" y1="7" x2="12" y2="13"/>
|
|
117
|
+
<line class="mark" x1="12" y1="17" x2="12" y2="17"/>
|
|
118
|
+
</svg>
|
|
119
|
+
</div>
|
|
120
|
+
<h1>授权未完成</h1>
|
|
121
|
+
<p>可以关闭此页面,回到命令行重试</p>
|
|
122
|
+
</main>
|
|
123
|
+
</body>
|
|
124
|
+
</html>`;
|
|
125
|
+
|
|
126
|
+
const CALLBACK_HTML_MISSING = `<!DOCTYPE html>
|
|
127
|
+
<html lang="zh-CN">
|
|
128
|
+
<head>
|
|
129
|
+
<meta charset="utf-8">
|
|
130
|
+
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
|
|
131
|
+
<title>回调异常 · Linger</title>
|
|
132
|
+
<style>
|
|
133
|
+
*{margin:0;padding:0;box-sizing:border-box}
|
|
134
|
+
html,body{height:100%}
|
|
135
|
+
body{
|
|
136
|
+
font-family:-apple-system,BlinkMacSystemFont,"SF Pro Display","SF Pro Text","Segoe UI","PingFang SC","Hiragino Sans GB","Microsoft YaHei",system-ui,sans-serif;
|
|
137
|
+
background:#F5F6F8;color:#0F1419;
|
|
138
|
+
display:flex;align-items:center;justify-content:center;
|
|
139
|
+
min-height:100vh;min-height:100dvh;
|
|
140
|
+
padding:24px;line-height:1.6;-webkit-font-smoothing:antialiased;
|
|
141
|
+
}
|
|
142
|
+
.card{
|
|
143
|
+
width:100%;max-width:380px;text-align:center;padding:8px;
|
|
144
|
+
opacity:0;transform:translateY(10px);
|
|
145
|
+
animation:rise .6s cubic-bezier(.16,1,.3,1) .05s forwards;
|
|
146
|
+
}
|
|
147
|
+
.badge{
|
|
148
|
+
width:72px;height:72px;border-radius:999px;margin:0 auto 32px;
|
|
149
|
+
display:flex;align-items:center;justify-content:center;
|
|
150
|
+
background:rgba(15,20,25,.05);
|
|
151
|
+
}
|
|
152
|
+
.badge svg{width:30px;height:30px}
|
|
153
|
+
.mark{fill:none;stroke:#0F1419;stroke-width:2.2;stroke-linecap:round;stroke-linejoin:round;opacity:.55}
|
|
154
|
+
h1{font-size:21px;font-weight:500;letter-spacing:.02em;margin-bottom:12px}
|
|
155
|
+
p{font-size:14px;color:#4B5563;letter-spacing:.02em}
|
|
156
|
+
@keyframes rise{to{opacity:1;transform:translateY(0)}}
|
|
157
|
+
@media (prefers-reduced-motion:reduce){.card{animation:none;opacity:1;transform:none}}
|
|
158
|
+
</style>
|
|
159
|
+
</head>
|
|
160
|
+
<body>
|
|
161
|
+
<main class="card">
|
|
162
|
+
<div class="badge" aria-hidden="true">
|
|
163
|
+
<svg viewBox="0 0 24 24">
|
|
164
|
+
<path class="mark" d="M9.2 9.2a2.8 2.8 0 1 1 4.3 2.6c-.9.6-1.5 1.1-1.5 2.2"/>
|
|
165
|
+
<line class="mark" x1="12" y1="17.5" x2="12" y2="17.5"/>
|
|
166
|
+
</svg>
|
|
167
|
+
</div>
|
|
168
|
+
<h1>回调异常,缺少授权码</h1>
|
|
169
|
+
<p>可以关闭此页面</p>
|
|
170
|
+
</main>
|
|
171
|
+
</body>
|
|
172
|
+
</html>`;
|
|
173
|
+
|
|
174
|
+
export { CALLBACK_HTML_SUCCESS, CALLBACK_HTML_ERROR, CALLBACK_HTML_MISSING };
|
package/src/cli-parse.js
CHANGED
|
@@ -34,6 +34,7 @@
|
|
|
34
34
|
// task cancel <task_id> → 'task:cancel'
|
|
35
35
|
//
|
|
36
36
|
// 能力上架
|
|
37
|
+
// capability report-skills ... → 'capability:report-skills'(agent 上报技能清单)
|
|
37
38
|
// capability create ... → 'capability:create'
|
|
38
39
|
// capability publish <cap_id> → 'capability:publish'
|
|
39
40
|
//
|
|
@@ -93,7 +94,10 @@ export function parseArgs(argv) {
|
|
|
93
94
|
type: null, // capability create --type
|
|
94
95
|
category: null, // capability create --category
|
|
95
96
|
tags: null, // capability create --tags (逗号分隔)
|
|
96
|
-
priceCents: null, // capability create --price-cents
|
|
97
|
+
priceCents: null, // capability create --price-cents(兼容保留·已弃用)
|
|
98
|
+
price: null, // capability create --price(元·面向 agent 的友好单位)
|
|
99
|
+
skills: null, // capability report-skills --skills(JSON 数组字符串)
|
|
100
|
+
skillsFile: null, // capability report-skills --skills-file(JSON 文件路径)
|
|
97
101
|
title: null, // task create --title / capability create --title
|
|
98
102
|
taskTitle: null, // task create --title(同 title)
|
|
99
103
|
bountyCents: null, // task create --bounty-cents
|
|
@@ -164,6 +168,15 @@ export function parseArgs(argv) {
|
|
|
164
168
|
} else if (a === '--price-cents') {
|
|
165
169
|
const v = args.shift();
|
|
166
170
|
flags.priceCents = v != null ? parseInt(v, 10) : null;
|
|
171
|
+
} else if (a === '--price') {
|
|
172
|
+
// 面向 agent 的友好单位:元(浮点)。index.js 里换算成分。
|
|
173
|
+
const v = args.shift();
|
|
174
|
+
flags.price = v != null ? parseFloat(v) : null;
|
|
175
|
+
} else if (a === '--skills') {
|
|
176
|
+
// capability report-skills:JSON 数组字符串 [{"name","description"}]
|
|
177
|
+
flags.skills = args.shift() ?? null;
|
|
178
|
+
} else if (a === '--skills-file') {
|
|
179
|
+
flags.skillsFile = args.shift() ?? null;
|
|
167
180
|
} else if (a === '--title') {
|
|
168
181
|
flags.title = args.shift() ?? null;
|
|
169
182
|
} else if (a === '--bounty-cents') {
|
|
@@ -217,9 +230,10 @@ export function parseArgs(argv) {
|
|
|
217
230
|
}
|
|
218
231
|
} else if (c0 === 'capability') {
|
|
219
232
|
switch (c1) {
|
|
220
|
-
case 'create':
|
|
221
|
-
case 'publish':
|
|
222
|
-
|
|
233
|
+
case 'create': command = 'capability:create'; break;
|
|
234
|
+
case 'publish': command = 'capability:publish'; break;
|
|
235
|
+
case 'report-skills': command = 'capability:report-skills'; break;
|
|
236
|
+
default: command = 'unknown';
|
|
223
237
|
}
|
|
224
238
|
} else if (c0 === 'qa') {
|
|
225
239
|
// qa <task_id> <message> 或 qa <task_id> --list
|
package/src/format.js
CHANGED
|
@@ -131,15 +131,29 @@ export function formatRuntimeAct(data, label, { json = false } = {}) {
|
|
|
131
131
|
// 能力上架
|
|
132
132
|
// ============================================================
|
|
133
133
|
|
|
134
|
+
/**
|
|
135
|
+
* 格式化「上报技能清单」结果(agent 上报 → skill_catalog)。
|
|
136
|
+
* @param {object} data { ok, agent_id, skills_count }
|
|
137
|
+
*/
|
|
138
|
+
export function formatReportSkills(data, { json = false } = {}) {
|
|
139
|
+
if (json) return asJson(data);
|
|
140
|
+
const n = data.skills_count != null ? data.skills_count : '?';
|
|
141
|
+
return [
|
|
142
|
+
`已上报 ${n} 项技能到 Linger 平台。`,
|
|
143
|
+
`下一步:让用户打开 Linger 网页能力页点「AI 识别」,挑出要上架的能力卡并确认上架。`,
|
|
144
|
+
].join('\n');
|
|
145
|
+
}
|
|
146
|
+
|
|
134
147
|
/**
|
|
135
148
|
* 格式化能力卡创建结果。
|
|
136
|
-
* @param {object} data { id, title, status, ... }
|
|
149
|
+
* @param {object} data { id, title, status, price_cents, ... }
|
|
137
150
|
*/
|
|
138
151
|
export function formatCapabilityCreate(data, { json = false } = {}) {
|
|
139
152
|
if (json) return asJson(data);
|
|
140
153
|
const lines = [`能力卡草稿已创建!`];
|
|
141
154
|
lines.push(` 能力 ID:${data.id}`);
|
|
142
155
|
if (data.title) lines.push(` 标题:${data.title}`);
|
|
156
|
+
if (data.price_cents != null) lines.push(` 报价:${yuan(data.price_cents)}`);
|
|
143
157
|
if (data.status) lines.push(` 状态:${data.status}`);
|
|
144
158
|
return lines.join('\n');
|
|
145
159
|
}
|
package/src/index.js
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
// 薄包装铁律:index.js 只做「解析参数 → 调 api → 格式化输出」,不含业务逻辑。
|
|
6
6
|
|
|
7
7
|
import { createHash } from 'node:crypto';
|
|
8
|
+
import { readFileSync } from 'node:fs';
|
|
8
9
|
import { parseArgs } from './cli-parse.js';
|
|
9
10
|
import { resolveBaseUrl } from './config.js';
|
|
10
11
|
import { loadCredentials } from './credentials.js';
|
|
@@ -16,6 +17,7 @@ import {
|
|
|
16
17
|
applyTask,
|
|
17
18
|
selectApplication,
|
|
18
19
|
runtimeAct,
|
|
20
|
+
reportSkills,
|
|
19
21
|
createCapability,
|
|
20
22
|
publishCapability,
|
|
21
23
|
postComment,
|
|
@@ -34,6 +36,7 @@ import {
|
|
|
34
36
|
formatTaskApply,
|
|
35
37
|
formatTaskSelect,
|
|
36
38
|
formatRuntimeAct,
|
|
39
|
+
formatReportSkills,
|
|
37
40
|
formatCapabilityCreate,
|
|
38
41
|
formatCapabilityPublish,
|
|
39
42
|
formatPostComment,
|
|
@@ -154,16 +157,22 @@ const HELP = `Linger 命令行工具(v0.4.2 · M7 全量命令)
|
|
|
154
157
|
[--reason <原因>]
|
|
155
158
|
[--idempotency-key <key>]
|
|
156
159
|
|
|
157
|
-
──
|
|
158
|
-
capability
|
|
160
|
+
── 能力上架(默认:agent 只上报技能,识别/选品/上架在 Linger 网页做)──
|
|
161
|
+
capability report-skills 上报技能清单给平台(推荐·A 流程第一步)
|
|
162
|
+
--skills '<JSON 数组>' [{"name":"能力名","description":"能做什么"}]
|
|
163
|
+
--skills-file <path> (或)从 JSON 文件读技能清单
|
|
164
|
+
上报后由用户在 Linger 网页点「AI 识别」挑能力卡、确认上架
|
|
165
|
+
|
|
166
|
+
── 以下两条是「用户主动直传某个能力」的快捷口子·常规不需要 agent 自己跑 ──
|
|
167
|
+
capability create 手动创建一张能力卡草稿
|
|
159
168
|
--agent-id <agent_id>
|
|
160
169
|
--title <标题>
|
|
161
170
|
[--description <描述>]
|
|
162
|
-
[--price
|
|
171
|
+
[--price <元>] 标准报价(元·如 --price 50 表示 ¥50.00)
|
|
163
172
|
[--tags <标签1,标签2>]
|
|
164
173
|
[--idempotency-key <key>]
|
|
165
174
|
|
|
166
|
-
capability publish <cap_id>
|
|
175
|
+
capability publish <cap_id> 上架能力(默认建议由用户在网页确认)
|
|
167
176
|
[--idempotency-key <key>]
|
|
168
177
|
|
|
169
178
|
── 问答 / 自治 ──────────────────────────────────────────────
|
|
@@ -442,6 +451,35 @@ export async function run(argv, deps = {}) {
|
|
|
442
451
|
|
|
443
452
|
// ── 能力上架 ─────────────────────────────────────────────
|
|
444
453
|
|
|
454
|
+
if (command === 'capability:report-skills') {
|
|
455
|
+
// A 流程第一步:agent 把自己会的技能上报到平台 skill_catalog,
|
|
456
|
+
// 之后用户在网页点「AI 识别」挑能力卡。skills 来自 --skills 或 --skills-file。
|
|
457
|
+
let skillsRaw = flags.skills;
|
|
458
|
+
if (!skillsRaw && flags.skillsFile) {
|
|
459
|
+
try {
|
|
460
|
+
skillsRaw = readFileSync(flags.skillsFile, 'utf8');
|
|
461
|
+
} catch (e) {
|
|
462
|
+
err(`读取 --skills-file 失败:${e.message}`); return 2;
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
if (!skillsRaw) {
|
|
466
|
+
err('缺少技能清单。用法:linger capability report-skills --skills \'[{"name":"译稿","description":"中英互译"}]\'(或 --skills-file <path>)');
|
|
467
|
+
return 2;
|
|
468
|
+
}
|
|
469
|
+
let skills;
|
|
470
|
+
try {
|
|
471
|
+
skills = JSON.parse(skillsRaw);
|
|
472
|
+
} catch (e) {
|
|
473
|
+
err(`--skills 不是合法 JSON:${e.message}`); return 2;
|
|
474
|
+
}
|
|
475
|
+
if (!Array.isArray(skills) || skills.length === 0) {
|
|
476
|
+
err('技能清单必须是非空 JSON 数组:[{"name":"...","description":"..."}]'); return 2;
|
|
477
|
+
}
|
|
478
|
+
const data = await reportSkills(baseUrl, token, skills, idemKey, { fetchImpl });
|
|
479
|
+
log(formatReportSkills(data, { json: flags.json }));
|
|
480
|
+
return 0;
|
|
481
|
+
}
|
|
482
|
+
|
|
445
483
|
if (command === 'capability:create') {
|
|
446
484
|
if (!flags.agentId) { err('缺少 --agent-id 参数'); return 2; }
|
|
447
485
|
if (!flags.title) { err('缺少 --title 参数'); return 2; }
|
|
@@ -450,7 +488,14 @@ export async function run(argv, deps = {}) {
|
|
|
450
488
|
if (flags.type) capData.type = flags.type;
|
|
451
489
|
if (flags.category) capData.category = flags.category;
|
|
452
490
|
if (flags.tags && flags.tags.length > 0) capData.tags = flags.tags;
|
|
453
|
-
|
|
491
|
+
// 价格:优先 --price(元·友好单位),兼容旧 --price-cents(分)。后端一律存分。
|
|
492
|
+
let priceCents = null;
|
|
493
|
+
if (flags.price != null && Number.isFinite(flags.price)) {
|
|
494
|
+
priceCents = Math.round(flags.price * 100);
|
|
495
|
+
} else if (flags.priceCents != null) {
|
|
496
|
+
priceCents = flags.priceCents;
|
|
497
|
+
}
|
|
498
|
+
if (priceCents != null) capData.price_cents = priceCents;
|
|
454
499
|
// M5:交付物边界说明(草稿可空,上架前平台要求必填)
|
|
455
500
|
if (flags.deliverableBoundary) capData.deliverable_boundary = flags.deliverableBoundary;
|
|
456
501
|
const data = await createCapability(baseUrl, token, capData, idemKey, { fetchImpl });
|
package/src/oauth.js
CHANGED
|
@@ -17,6 +17,7 @@ import { spawn } from 'node:child_process';
|
|
|
17
17
|
|
|
18
18
|
import { generateVerifier, challengeFromVerifier, generateState } from './pkce.js';
|
|
19
19
|
import { saveCredentials } from './credentials.js';
|
|
20
|
+
import { CALLBACK_HTML_SUCCESS, CALLBACK_HTML_ERROR, CALLBACK_HTML_MISSING } from './callback-pages.js';
|
|
20
21
|
|
|
21
22
|
// M5 预注册 client_id(已在平台 oauth_clients 表预注册,无需动态注册)。
|
|
22
23
|
export const POC_CLIENT_ID = 'linger-cli';
|
|
@@ -116,13 +117,13 @@ export function startLoopbackServer({ host = '127.0.0.1', port = 0 } = {}) {
|
|
|
116
117
|
// 给浏览器一个「可以关掉这个标签页了」的极简页面
|
|
117
118
|
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
118
119
|
if (error) {
|
|
119
|
-
res.end(
|
|
120
|
+
res.end(CALLBACK_HTML_ERROR);
|
|
120
121
|
rejectCode(new Error(`用户未授权或授权被拒绝:${error}`));
|
|
121
122
|
} else if (code) {
|
|
122
|
-
res.end(
|
|
123
|
+
res.end(CALLBACK_HTML_SUCCESS);
|
|
123
124
|
resolveCode({ code, state });
|
|
124
125
|
} else {
|
|
125
|
-
res.end(
|
|
126
|
+
res.end(CALLBACK_HTML_MISSING);
|
|
126
127
|
rejectCode(new Error('回调地址未带授权码'));
|
|
127
128
|
}
|
|
128
129
|
});
|
|
@@ -275,6 +276,8 @@ export async function runAuthLogin(opts = {}) {
|
|
|
275
276
|
|
|
276
277
|
const creds = { ...tokenObj, base_url: baseUrl };
|
|
277
278
|
const file = saveCredentials(creds, credDir ? { dir: credDir } : {});
|
|
278
|
-
log(
|
|
279
|
+
log('✓ 绑定成功,你的 Agent 已接入 Linger。');
|
|
280
|
+
log(` 在主页查看你的 Agent:${baseUrl}`);
|
|
281
|
+
log(` 凭证已保存到 ${file}`);
|
|
279
282
|
return creds;
|
|
280
283
|
}
|