u1s1-cli 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 +35 -0
- package/dist/api.js +18 -0
- package/dist/config.js +30 -0
- package/dist/index.js +88 -0
- package/dist/login.js +46 -0
- package/dist/usage.js +28 -0
- package/package.json +37 -0
package/README.md
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# u1s1 — 有一说一,最省心的 AI 编程搭子
|
|
2
|
+
|
|
3
|
+
在终端里用中文说需求,AI 帮你读文件、改代码、跑命令。不用买模型、不用配 API、不用懂那些名词,注册就送**每月 $2 免费额度**(普通人根本用不完)。
|
|
4
|
+
|
|
5
|
+
## 三步开始
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
# 1. 安装(需要 Node.js 20+)
|
|
9
|
+
npm i -g u1s1-cli
|
|
10
|
+
|
|
11
|
+
# 2. 第一次运行会引导你去 u1s1.io 注册领 Key(30 秒,不用绑卡)
|
|
12
|
+
u1s1
|
|
13
|
+
|
|
14
|
+
# 3. 进到你的项目文件夹,像发微信一样说需求
|
|
15
|
+
cd 我的项目
|
|
16
|
+
u1s1
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## 常用命令
|
|
20
|
+
|
|
21
|
+
| 命令 | 作用 |
|
|
22
|
+
|---|---|
|
|
23
|
+
| `u1s1` | 进入交互模式,直接开聊 |
|
|
24
|
+
| `u1s1 -p "把 README 里的错别字修一下"` | 一句话模式,干完就退出 |
|
|
25
|
+
| `u1s1 usage` | 看本月额度用了多少 |
|
|
26
|
+
| `u1s1 login` / `u1s1 logout` | 登录 / 退出 |
|
|
27
|
+
|
|
28
|
+
## 有一说一
|
|
29
|
+
|
|
30
|
+
- **背后模型**:DeepSeek V4 Flash,1M 超长上下文,写代码很能打,成本只有大牌模型的约 1/50。
|
|
31
|
+
- **怎么收费**:额度按 API 实际成本扣,不加价;每月 1 号自动重置。
|
|
32
|
+
- **额度不够**:邀请朋友,你俩各得 $1 加量,永不过期 → [u1s1.io/dashboard](https://u1s1.io/dashboard)
|
|
33
|
+
- **内核**:基于开源的 [pi](https://pi.dev) coding agent,会话管理、斜杠命令、皮肤等能力全都有。
|
|
34
|
+
|
|
35
|
+
官网:[u1s1.io](https://u1s1.io)
|
package/dist/api.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export async function fetchMe(cfg) {
|
|
2
|
+
if (!cfg.apiKey)
|
|
3
|
+
throw new Error("没有配置 API Key");
|
|
4
|
+
let resp;
|
|
5
|
+
try {
|
|
6
|
+
resp = await fetch(`${cfg.baseUrl}/me`, {
|
|
7
|
+
headers: { authorization: `Bearer ${cfg.apiKey}` },
|
|
8
|
+
});
|
|
9
|
+
}
|
|
10
|
+
catch {
|
|
11
|
+
throw new Error(`连不上 ${cfg.baseUrl},检查一下网络?`);
|
|
12
|
+
}
|
|
13
|
+
if (resp.status === 401)
|
|
14
|
+
throw new Error("这把 Key 不对或已失效,去 https://u1s1.io/dashboard 看看");
|
|
15
|
+
if (!resp.ok)
|
|
16
|
+
throw new Error(`服务端返回 ${resp.status},稍后再试`);
|
|
17
|
+
return (await resp.json());
|
|
18
|
+
}
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
export const DEFAULT_BASE_URL = "https://api.u1s1.io/v1";
|
|
5
|
+
export const MODEL_ID = "deepseek/deepseek-v4-flash";
|
|
6
|
+
export const PROVIDER_ID = "u1s1";
|
|
7
|
+
export const u1s1Dir = join(homedir(), ".u1s1");
|
|
8
|
+
const configFile = join(u1s1Dir, "config.json");
|
|
9
|
+
/** pi keeps auth/models/settings/sessions under this dir — isolated from any real pi install. */
|
|
10
|
+
export const agentDir = join(u1s1Dir, "agent");
|
|
11
|
+
export function loadConfig() {
|
|
12
|
+
let file = {};
|
|
13
|
+
if (existsSync(configFile)) {
|
|
14
|
+
try {
|
|
15
|
+
file = JSON.parse(readFileSync(configFile, "utf8"));
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
// corrupted config falls back to defaults; login rewrites it
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return {
|
|
22
|
+
apiKey: process.env["U1S1_API_KEY"] || file.apiKey,
|
|
23
|
+
baseUrl: process.env["U1S1_BASE_URL"] || file.baseUrl || DEFAULT_BASE_URL,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
export function saveConfig(cfg) {
|
|
27
|
+
mkdirSync(u1s1Dir, { recursive: true, mode: 0o700 });
|
|
28
|
+
writeFileSync(configFile, JSON.stringify(cfg, null, 2) + "\n");
|
|
29
|
+
chmodSync(configFile, 0o600);
|
|
30
|
+
}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { createRequire } from "node:module";
|
|
5
|
+
import { agentDir, loadConfig, MODEL_ID, PROVIDER_ID } from "./config.js";
|
|
6
|
+
const require = createRequire(import.meta.url);
|
|
7
|
+
const VERSION = require("../package.json").version;
|
|
8
|
+
const BRAND_APPEND = `## u1s1
|
|
9
|
+
|
|
10
|
+
你运行在 u1s1(有一说一)里 —— 一个面向编程新手的中文 AI 编程助手。用户很可能不熟悉编程术语:
|
|
11
|
+
- 默认用中文回复;代码、命令、报错原文保持英文。
|
|
12
|
+
- 解释问题时说人话,别堆术语;必要时用一句话打比方。
|
|
13
|
+
- 改动前先说明打算做什么,改完用一两句话总结改了哪里。
|
|
14
|
+
- 用户描述模糊时,先猜最可能的意图并确认,不要长篇追问。
|
|
15
|
+
`;
|
|
16
|
+
/** pi auto-appends <agentDir>/APPEND_SYSTEM.md to its system prompt — our branding hook. */
|
|
17
|
+
function ensureBrandPrompt() {
|
|
18
|
+
mkdirSync(agentDir, { recursive: true });
|
|
19
|
+
const p = join(agentDir, "APPEND_SYSTEM.md");
|
|
20
|
+
if (!existsSync(p) || readFileSync(p, "utf8") !== BRAND_APPEND) {
|
|
21
|
+
writeFileSync(p, BRAND_APPEND);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
async function runAgent(cfg, args) {
|
|
25
|
+
ensureBrandPrompt();
|
|
26
|
+
// must be set before pi reads them (getAgentDir() reads at call time, env at import is fine too)
|
|
27
|
+
process.env["PI_CODING_AGENT_DIR"] = agentDir;
|
|
28
|
+
process.env["U1S1_API_KEY"] = cfg.apiKey;
|
|
29
|
+
const { main } = await import("@earendil-works/pi-coding-agent");
|
|
30
|
+
const extension = [
|
|
31
|
+
{
|
|
32
|
+
name: "u1s1",
|
|
33
|
+
factory: (pi) => {
|
|
34
|
+
pi.registerProvider(PROVIDER_ID, {
|
|
35
|
+
name: "u1s1",
|
|
36
|
+
baseUrl: cfg.baseUrl,
|
|
37
|
+
api: "openai-completions",
|
|
38
|
+
apiKey: "$U1S1_API_KEY",
|
|
39
|
+
models: [
|
|
40
|
+
{
|
|
41
|
+
id: MODEL_ID,
|
|
42
|
+
name: "DeepSeek V4 Flash (u1s1)",
|
|
43
|
+
reasoning: false,
|
|
44
|
+
input: ["text"],
|
|
45
|
+
cost: { input: 0.14, output: 0.28, cacheRead: 0, cacheWrite: 0 },
|
|
46
|
+
contextWindow: 1_048_576,
|
|
47
|
+
maxTokens: 65_536,
|
|
48
|
+
},
|
|
49
|
+
],
|
|
50
|
+
});
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
];
|
|
54
|
+
const hasModelArg = args.some((a) => a === "--model" || a.startsWith("--model=") || a === "--provider");
|
|
55
|
+
const finalArgs = hasModelArg ? args : ["--model", `${PROVIDER_ID}/${MODEL_ID}`, ...args];
|
|
56
|
+
await main(finalArgs, { extensionFactories: extension });
|
|
57
|
+
}
|
|
58
|
+
async function run() {
|
|
59
|
+
const args = process.argv.slice(2);
|
|
60
|
+
const cmd = args[0];
|
|
61
|
+
if (cmd === "--version" || cmd === "-v") {
|
|
62
|
+
console.log(`u1s1 v${VERSION}`);
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
if (cmd === "login") {
|
|
66
|
+
const { login } = await import("./login.js");
|
|
67
|
+
await login(args[1]);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
if (cmd === "usage") {
|
|
71
|
+
const { usage } = await import("./usage.js");
|
|
72
|
+
await usage();
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
if (cmd === "logout") {
|
|
76
|
+
const { saveConfig } = await import("./config.js");
|
|
77
|
+
saveConfig({ ...loadConfig(), apiKey: undefined });
|
|
78
|
+
console.log("已退出登录。");
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
const { ensureAuth } = await import("./login.js");
|
|
82
|
+
const cfg = await ensureAuth();
|
|
83
|
+
await runAgent(cfg, args);
|
|
84
|
+
}
|
|
85
|
+
run().catch((e) => {
|
|
86
|
+
console.error(e instanceof Error ? e.message : e);
|
|
87
|
+
process.exit(1);
|
|
88
|
+
});
|
package/dist/login.js
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { createInterface } from "node:readline/promises";
|
|
2
|
+
import { fetchMe } from "./api.js";
|
|
3
|
+
import { loadConfig, saveConfig } from "./config.js";
|
|
4
|
+
const DASHBOARD_URL = "https://u1s1.io/dashboard";
|
|
5
|
+
function tryOpenBrowser(url) {
|
|
6
|
+
import("node:child_process")
|
|
7
|
+
.then(({ spawn }) => {
|
|
8
|
+
const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
|
|
9
|
+
spawn(cmd, [url], { detached: true, stdio: "ignore" }).on("error", () => { }).unref();
|
|
10
|
+
})
|
|
11
|
+
.catch(() => { });
|
|
12
|
+
}
|
|
13
|
+
export async function login(keyArg) {
|
|
14
|
+
const cfg = loadConfig();
|
|
15
|
+
let key = keyArg?.trim();
|
|
16
|
+
if (!key) {
|
|
17
|
+
console.log("");
|
|
18
|
+
console.log(" u1s1 需要一把 API Key(免费):");
|
|
19
|
+
console.log(` 1. 打开 ${DASHBOARD_URL} 注册/登录(30 秒,送每月 $2 额度)`);
|
|
20
|
+
console.log(" 2. 复制你的 API Key,粘贴到下面");
|
|
21
|
+
console.log("");
|
|
22
|
+
tryOpenBrowser(DASHBOARD_URL);
|
|
23
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
24
|
+
key = (await rl.question(" 粘贴 API Key: ")).trim();
|
|
25
|
+
rl.close();
|
|
26
|
+
}
|
|
27
|
+
if (!key.startsWith("u1s1-")) {
|
|
28
|
+
console.error(" 这不像一把 u1s1 的 Key(应该以 u1s1- 开头),再看看?");
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|
|
31
|
+
const next = { ...cfg, apiKey: key };
|
|
32
|
+
const me = await fetchMe(next).catch((e) => {
|
|
33
|
+
console.error(` 验证失败:${e.message}`);
|
|
34
|
+
process.exit(1);
|
|
35
|
+
});
|
|
36
|
+
saveConfig(next);
|
|
37
|
+
console.log(` ✓ 登录成功${me.email ? `(${me.email})` : ""},本月还剩 $${me.remaining_usd} 额度。`);
|
|
38
|
+
return next;
|
|
39
|
+
}
|
|
40
|
+
/** Returns a config that definitely has an apiKey, prompting the user if needed. */
|
|
41
|
+
export async function ensureAuth() {
|
|
42
|
+
const cfg = loadConfig();
|
|
43
|
+
if (cfg.apiKey)
|
|
44
|
+
return cfg;
|
|
45
|
+
return login();
|
|
46
|
+
}
|
package/dist/usage.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { fetchMe } from "./api.js";
|
|
2
|
+
import { loadConfig } from "./config.js";
|
|
3
|
+
function bar(ratio, width = 24) {
|
|
4
|
+
const filled = Math.round(Math.max(0, Math.min(1, ratio)) * width);
|
|
5
|
+
return "█".repeat(filled) + "░".repeat(width - filled);
|
|
6
|
+
}
|
|
7
|
+
export async function usage() {
|
|
8
|
+
const cfg = loadConfig();
|
|
9
|
+
if (!cfg.apiKey) {
|
|
10
|
+
console.error("还没登录,先跑 u1s1 login");
|
|
11
|
+
process.exit(1);
|
|
12
|
+
}
|
|
13
|
+
const me = await fetchMe(cfg).catch((e) => {
|
|
14
|
+
console.error(e.message);
|
|
15
|
+
process.exit(1);
|
|
16
|
+
});
|
|
17
|
+
const total = me.monthly_free_usd + me.bonus_balance_usd;
|
|
18
|
+
const remainRatio = total > 0 ? me.remaining_usd / total : 0;
|
|
19
|
+
console.log("");
|
|
20
|
+
console.log(` 账号 ${me.email ?? "(未绑定邮箱)"}`);
|
|
21
|
+
console.log(` 本月已用 $${me.mtd_usd}`);
|
|
22
|
+
console.log(` 剩余额度 $${me.remaining_usd} ${bar(remainRatio)}`);
|
|
23
|
+
if (me.bonus_balance_usd > 0)
|
|
24
|
+
console.log(` 其中加量 $${me.bonus_balance_usd}(邀请所得,不过期)`);
|
|
25
|
+
console.log("");
|
|
26
|
+
console.log(" 邀请朋友双方各得加量 → https://u1s1.io/dashboard");
|
|
27
|
+
console.log("");
|
|
28
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "u1s1-cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "u1s1 — 有一说一,最省心的 AI 编程搭子。终端里用中文说需求,AI 帮你读文件、改代码、跑命令。",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"u1s1": "dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist"
|
|
11
|
+
],
|
|
12
|
+
"engines": {
|
|
13
|
+
"node": ">=20"
|
|
14
|
+
},
|
|
15
|
+
"scripts": {
|
|
16
|
+
"build": "tsc",
|
|
17
|
+
"dev": "tsx src/index.ts",
|
|
18
|
+
"typecheck": "tsc --noEmit"
|
|
19
|
+
},
|
|
20
|
+
"keywords": [
|
|
21
|
+
"ai",
|
|
22
|
+
"cli",
|
|
23
|
+
"coding-agent",
|
|
24
|
+
"deepseek",
|
|
25
|
+
"u1s1"
|
|
26
|
+
],
|
|
27
|
+
"license": "MIT",
|
|
28
|
+
"homepage": "https://u1s1.io",
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"@earendil-works/pi-coding-agent": "0.84.1"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"typescript": "^5.9.2",
|
|
34
|
+
"tsx": "^4.20.0",
|
|
35
|
+
"@types/node": "^22.15.0"
|
|
36
|
+
}
|
|
37
|
+
}
|