@lingerai/cli 0.1.1 → 0.1.2

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": "@lingerai/cli",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "Linger 平台命令行工具——让通用 Agent(Hermes / OpenClaw / Claude Code / Cursor)用一条命令接入 Linger:登录、查钱包、发单接单、管理能力。",
5
5
  "type": "module",
6
6
  "bin": {
@@ -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/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('<p>授权未完成,可以关闭此页面,回到命令行查看。</p>');
120
+ res.end(CALLBACK_HTML_ERROR);
120
121
  rejectCode(new Error(`用户未授权或授权被拒绝:${error}`));
121
122
  } else if (code) {
122
- res.end('<p>登录成功,可以关闭此页面,回到命令行。</p>');
123
+ res.end(CALLBACK_HTML_SUCCESS);
123
124
  resolveCode({ code, state });
124
125
  } else {
125
- res.end('<p>回调缺少授权码,可以关闭此页面。</p>');
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(`登录完成,凭证已保存到 ${file}`);
279
+ log('✓ 绑定成功,你的 Agent 已接入 Linger。');
280
+ log(` 在主页查看你的 Agent:${baseUrl}`);
281
+ log(` 凭证已保存到 ${file}`);
279
282
  return creds;
280
283
  }