u1s1-cli 0.20.0 → 0.20.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/README.md +4 -0
- package/dist/agent-setup.js +1 -1
- package/dist/api.d.ts +3 -0
- package/dist/brand.js +3 -3
- package/dist/config.d.ts +3 -0
- package/dist/config.js +4 -0
- package/dist/device-auth.d.ts +4 -2
- package/dist/device-auth.js +19 -4
- package/dist/web.js +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -46,6 +46,10 @@ u1s1
|
|
|
46
46
|
| `u1s1 import` | 从 Claude Code / Codex 导入历史对话,之后 `/resume` 就能接着聊 |
|
|
47
47
|
| `u1s1 login` / `u1s1 logout` | 登录 / 退出 |
|
|
48
48
|
|
|
49
|
+
## 在终端粘贴图片
|
|
50
|
+
|
|
51
|
+
先切换到支持视觉输入的模型(例如 `u1s1 model deepseek-v4-flash-vision-exp`),复制截图或图片后,在 u1s1 输入框按 `Ctrl+V` 即可附加图片。macOS 这里也使用 `Ctrl+V`;`Cmd+V` 由 Terminal/iTerm 接管,只适合粘贴文本,无法把剪贴板图片交给 CLI。
|
|
52
|
+
|
|
49
53
|
## 有一说一
|
|
50
54
|
|
|
51
55
|
- **背后模型**:默认 DeepSeek V4 Flash(1M 上下文,每天免费 $2);难题可随时 `u1s1 model grok` 切 Grok 4.6。对话里 `/model` 也会记住,下次启动还是这个。
|
package/dist/agent-setup.js
CHANGED
|
@@ -337,7 +337,7 @@ export function toProviderModels(models) {
|
|
|
337
337
|
id: m.id,
|
|
338
338
|
name: m.name,
|
|
339
339
|
reasoning: m.reasoning,
|
|
340
|
-
input: ["text"],
|
|
340
|
+
input: m.vision ? ["text", "image"] : ["text"],
|
|
341
341
|
cost: m.cost,
|
|
342
342
|
contextWindow: m.contextWindow,
|
|
343
343
|
maxTokens: m.maxTokens,
|
package/dist/api.d.ts
CHANGED
|
@@ -35,6 +35,8 @@ export interface ApiModel {
|
|
|
35
35
|
id: string;
|
|
36
36
|
name: string;
|
|
37
37
|
reasoning: boolean;
|
|
38
|
+
/** Older gateways omit this field; missing means text-only. */
|
|
39
|
+
vision?: boolean;
|
|
38
40
|
context_length: number;
|
|
39
41
|
max_tokens: number;
|
|
40
42
|
price: {
|
|
@@ -72,6 +74,7 @@ export interface ApiEndpoint {
|
|
|
72
74
|
id: string;
|
|
73
75
|
name?: string;
|
|
74
76
|
reasoning?: boolean;
|
|
77
|
+
vision?: boolean;
|
|
75
78
|
context_window?: number;
|
|
76
79
|
max_tokens?: number;
|
|
77
80
|
}[];
|
package/dist/brand.js
CHANGED
|
@@ -56,9 +56,9 @@ export function renderBrandHeader(theme, version, cwd, width, notice, announceme
|
|
|
56
56
|
(notice ? ` ${theme.fg("accent", notice)}` : "");
|
|
57
57
|
const brand = theme.fg("muted", `${BRAND_CN} · ${BRAND_TAGLINE}`);
|
|
58
58
|
const dir = theme.fg("dim", `cwd: ${formatHomePath(cwd)}`);
|
|
59
|
-
const hints = theme.fg("dim", "/help
|
|
59
|
+
const hints = theme.fg("dim", "/help · Ctrl+V 图片 · Shift+Enter 换行 · Esc 中断");
|
|
60
60
|
if (width < ART_WIDTH + 4) {
|
|
61
|
-
const lines = ["", ` ${theme.fg("accent", "✻")} ${name} ${theme.fg("muted", BRAND_CN)}`, ` ${dir}`];
|
|
61
|
+
const lines = ["", ` ${theme.fg("accent", "✻")} ${name} ${theme.fg("muted", BRAND_CN)}`, ` ${dir}`, ` ${theme.fg("dim", "Ctrl+V 粘贴图片")}`];
|
|
62
62
|
if (announcement)
|
|
63
63
|
lines.push(` ${announcementLine(theme, announcement)}`);
|
|
64
64
|
return [...lines, ""];
|
|
@@ -66,7 +66,7 @@ export function renderBrandHeader(theme, version, cwd, width, notice, announceme
|
|
|
66
66
|
const art = HERO_ART.map((line) => ` ${paintArt(theme, line)}`);
|
|
67
67
|
// widest info row (hints) needs 46 cols beside the 28-col wordmark
|
|
68
68
|
if (width < ART_WIDTH + 46) {
|
|
69
|
-
const lines = ["", ...art, "", ` ${name} ${brand}`, ` ${dir}`];
|
|
69
|
+
const lines = ["", ...art, "", ` ${name} ${brand}`, ` ${dir}`, ` ${hints}`];
|
|
70
70
|
if (announcement)
|
|
71
71
|
lines.push(` ${announcementLine(theme, announcement)}`);
|
|
72
72
|
return [...lines, ""];
|
package/dist/config.d.ts
CHANGED
|
@@ -16,6 +16,7 @@ export declare function apiModelToDef(m: {
|
|
|
16
16
|
id: string;
|
|
17
17
|
name: string;
|
|
18
18
|
reasoning: boolean;
|
|
19
|
+
vision?: boolean;
|
|
19
20
|
context_length: number;
|
|
20
21
|
max_tokens: number;
|
|
21
22
|
price: {
|
|
@@ -30,6 +31,7 @@ export interface ModelDef {
|
|
|
30
31
|
/** short aliases accepted by `u1s1 model <name>` */
|
|
31
32
|
aliases: string[];
|
|
32
33
|
reasoning: boolean;
|
|
34
|
+
vision: boolean;
|
|
33
35
|
contextWindow: number;
|
|
34
36
|
maxTokens: number;
|
|
35
37
|
/** USD per million tokens — display/estimation only; billing is server-side */
|
|
@@ -73,6 +75,7 @@ export declare function apiEndpointToCustom(e: {
|
|
|
73
75
|
id: string;
|
|
74
76
|
name?: string;
|
|
75
77
|
reasoning?: boolean;
|
|
78
|
+
vision?: boolean;
|
|
76
79
|
context_window?: number;
|
|
77
80
|
max_tokens?: number;
|
|
78
81
|
}[];
|
package/dist/config.js
CHANGED
|
@@ -38,6 +38,7 @@ export function apiModelToDef(m) {
|
|
|
38
38
|
name: m.name,
|
|
39
39
|
aliases: defaultAliases(m.id),
|
|
40
40
|
reasoning: m.reasoning,
|
|
41
|
+
vision: m.vision === true,
|
|
41
42
|
contextWindow: m.context_length,
|
|
42
43
|
maxTokens: m.max_tokens,
|
|
43
44
|
cost: {
|
|
@@ -55,6 +56,7 @@ export const MODELS = [
|
|
|
55
56
|
name: "DeepSeek V4 Flash (u1s1)",
|
|
56
57
|
aliases: ["deepseek", "flash", "v4-flash"],
|
|
57
58
|
reasoning: false,
|
|
59
|
+
vision: false,
|
|
58
60
|
contextWindow: 1_048_576,
|
|
59
61
|
maxTokens: 384_000,
|
|
60
62
|
cost: { input: 0.14, output: 0.28, cacheRead: 0, cacheWrite: 0 },
|
|
@@ -65,6 +67,7 @@ export const MODELS = [
|
|
|
65
67
|
name: "Grok 4.6 (u1s1)",
|
|
66
68
|
aliases: ["grok", "grok4.6", "grok-4.6"],
|
|
67
69
|
reasoning: true,
|
|
70
|
+
vision: false,
|
|
68
71
|
contextWindow: 500_000,
|
|
69
72
|
maxTokens: 65_536,
|
|
70
73
|
cost: { input: 2, output: 6, cacheRead: 0, cacheWrite: 0 },
|
|
@@ -106,6 +109,7 @@ export function apiEndpointToCustom(e) {
|
|
|
106
109
|
name: m.name || m.id,
|
|
107
110
|
aliases: defaultAliases(m.id),
|
|
108
111
|
reasoning: !!m.reasoning,
|
|
112
|
+
vision: !!m.vision,
|
|
109
113
|
contextWindow: m.context_window ?? 128_000,
|
|
110
114
|
maxTokens: m.max_tokens ?? 16_384,
|
|
111
115
|
// 自有端点不经网关计费,价格未知,展示按 0 处理
|
package/dist/device-auth.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { webcrypto } from "node:crypto";
|
|
2
|
-
import type
|
|
2
|
+
import { type CliConfig } from "./config.js";
|
|
3
3
|
export declare function hasDeviceCredential(cfg: CliConfig): boolean;
|
|
4
4
|
export declare function generateDeviceKeyPair(): Promise<{
|
|
5
5
|
privateJwk: webcrypto.JsonWebKey;
|
|
@@ -12,10 +12,12 @@ interface SigningProxy {
|
|
|
12
12
|
baseUrl: string;
|
|
13
13
|
localKey: string;
|
|
14
14
|
token: string;
|
|
15
|
+
client: ClientSurface;
|
|
15
16
|
}
|
|
17
|
+
export type ClientSurface = "terminal" | "web" | "desktop" | "cloud";
|
|
16
18
|
/**
|
|
17
19
|
* pi accepts static provider headers only. Keep it behind a loopback proxy that
|
|
18
20
|
* replaces the local bearer credential with a fresh DPoP proof per request.
|
|
19
21
|
*/
|
|
20
|
-
export declare function ensureSigningProxy(cfg: CliConfig): Promise<SigningProxy>;
|
|
22
|
+
export declare function ensureSigningProxy(cfg: CliConfig, fallbackClient?: ClientSurface): Promise<SigningProxy>;
|
|
21
23
|
export {};
|
package/dist/device-auth.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createHash, randomBytes, randomUUID, webcrypto } from "node:crypto";
|
|
2
2
|
import { createServer } from "node:http";
|
|
3
|
+
import { VERSION } from "./config.js";
|
|
3
4
|
const enc = new TextEncoder();
|
|
4
5
|
let cachedPrivate;
|
|
5
6
|
function b64url(value) {
|
|
@@ -86,15 +87,23 @@ function requestHeaders(input) {
|
|
|
86
87
|
return headers;
|
|
87
88
|
}
|
|
88
89
|
let signingProxy;
|
|
90
|
+
function clientSurface(fallback) {
|
|
91
|
+
const explicit = process.env["U1S1_CLIENT"];
|
|
92
|
+
if (explicit === "terminal" || explicit === "web" || explicit === "desktop" || explicit === "cloud") {
|
|
93
|
+
return explicit;
|
|
94
|
+
}
|
|
95
|
+
return process.versions.electron ? "desktop" : fallback;
|
|
96
|
+
}
|
|
89
97
|
/**
|
|
90
98
|
* pi accepts static provider headers only. Keep it behind a loopback proxy that
|
|
91
99
|
* replaces the local bearer credential with a fresh DPoP proof per request.
|
|
92
100
|
*/
|
|
93
|
-
export async function ensureSigningProxy(cfg) {
|
|
101
|
+
export async function ensureSigningProxy(cfg, fallbackClient = "terminal") {
|
|
94
102
|
if (!hasDeviceCredential(cfg))
|
|
95
103
|
throw new Error("当前安装需要重新登录,以创建设备凭证");
|
|
104
|
+
const client = clientSurface(fallbackClient);
|
|
96
105
|
const current = signingProxy;
|
|
97
|
-
if (current && current.token === cfg.deviceToken)
|
|
106
|
+
if (current && current.token === cfg.deviceToken && current.client === client)
|
|
98
107
|
return current;
|
|
99
108
|
const localKey = `local-${randomBytes(32).toString("hex")}`;
|
|
100
109
|
const upstreamOrigin = new URL(cfg.baseUrl).origin;
|
|
@@ -118,9 +127,15 @@ export async function ensureSigningProxy(cfg) {
|
|
|
118
127
|
for await (const chunk of req)
|
|
119
128
|
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
120
129
|
const body = chunks.length ? Buffer.concat(chunks) : undefined;
|
|
130
|
+
const outboundHeaders = requestHeaders(req.headers);
|
|
131
|
+
// Set these at the final local hop so model calls and extension tools share
|
|
132
|
+
// exactly the same attribution, regardless of their upstream SDK defaults.
|
|
133
|
+
outboundHeaders.set("x-u1s1-client", client);
|
|
134
|
+
outboundHeaders.set("x-u1s1-version", VERSION);
|
|
135
|
+
outboundHeaders.set("x-u1s1-platform", `${process.platform}-${process.arch}`);
|
|
121
136
|
const upstream = await authorizedFetch(cfg, target, {
|
|
122
137
|
method: req.method ?? "GET",
|
|
123
|
-
headers:
|
|
138
|
+
headers: outboundHeaders,
|
|
124
139
|
body,
|
|
125
140
|
});
|
|
126
141
|
const headers = {};
|
|
@@ -148,6 +163,6 @@ export async function ensureSigningProxy(cfg) {
|
|
|
148
163
|
});
|
|
149
164
|
server.unref();
|
|
150
165
|
const port = server.address().port;
|
|
151
|
-
signingProxy = { baseUrl: `http://127.0.0.1:${port}/v1`, localKey, token: cfg.deviceToken };
|
|
166
|
+
signingProxy = { baseUrl: `http://127.0.0.1:${port}/v1`, localKey, token: cfg.deviceToken, client };
|
|
152
167
|
return signingProxy;
|
|
153
168
|
}
|
package/dist/web.js
CHANGED
|
@@ -47,7 +47,7 @@ export async function prepareWebEnv(cfg) {
|
|
|
47
47
|
console.error(" 获取模型列表失败,使用内置列表:", e.message);
|
|
48
48
|
}
|
|
49
49
|
await endpointsReady;
|
|
50
|
-
const signing = await ensureSigningProxy(cfg);
|
|
50
|
+
const signing = await ensureSigningProxy(cfg, "web");
|
|
51
51
|
const officialCfg = { ...cfg, baseUrl: signing.baseUrl, apiKey: signing.localKey };
|
|
52
52
|
ensureBrandPrompt(await shellReady);
|
|
53
53
|
// /workflow 提示词模板与 TUI 同源,web/桌面版也要有
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "u1s1-cli",
|
|
3
|
-
"version": "0.20.
|
|
3
|
+
"version": "0.20.2",
|
|
4
4
|
"description": "u1s1 — 有一说一,最省心的 AI 编程搭子。终端里用中文说需求,AI 帮你读文件、改代码、跑命令。",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"dev": "tsx src/index.ts",
|
|
28
28
|
"typecheck": "tsc --noEmit",
|
|
29
29
|
"prepublishOnly": "npm run build && npm test",
|
|
30
|
-
"test": "node scripts/render-regression.mjs",
|
|
30
|
+
"test": "tsx --test test/*.test.ts && node scripts/render-regression.mjs",
|
|
31
31
|
"postinstall": "node scripts/patch-pi.js",
|
|
32
32
|
"package": "bash ../../scripts/package.sh",
|
|
33
33
|
"upload-release": "bash ../../scripts/upload-release.sh",
|