u1s1-cli 0.9.3 → 0.11.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.
@@ -0,0 +1,32 @@
1
+ import { type CliConfig } from "./config.js";
2
+ /** pi auto-appends <agentDir>/APPEND_SYSTEM.md to its system prompt — our branding hook. */
3
+ export declare function ensureBrandPrompt(): void;
4
+ /** Defaults that don't overwrite values the user already set. */
5
+ export declare function ensureDefaultSettings(): void;
6
+ /** ≤0.4.0 wrote u1s1-dark/u1s1-light into the pi themes dir; remove them. */
7
+ export declare function cleanupBrandThemes(): void;
8
+ /**
9
+ * u1s1 provider for SDK-based frontends (`u1s1 web`): pi composes
10
+ * <agentDir>/models.json above registered providers, so this exposes the same
11
+ * models as the TUI's in-process extension without patching the frontend.
12
+ * apiKey stays an env reference — the launcher sets U1S1_API_KEY, the file
13
+ * itself holds no secret. Rewritten on every launch so baseUrl/model changes
14
+ * propagate; other providers a user may have added are preserved.
15
+ */
16
+ /**
17
+ * 联网工具经 <agentDir>/extensions 投影:pi 的资源加载器对 TUI 和 `u1s1 web`
18
+ * 都会扫描这个目录,两个前端从此共用同一份工具注册,不用各接一遍。
19
+ * 文件每次启动重写(baseUrl/服务端开关变化随之生效);apiKey 走 U1S1_API_KEY
20
+ * 环境变量,文件里不落密钥。U1S1_TOOLS_VIA_EXTENSION 守卫:旧版 CLI 仍在
21
+ * 进程内注册工具且不设该变量,残留的本文件在旧版下自动空转,避免双重注册。
22
+ */
23
+ export declare function writeWebToolsExtension(cfg: CliConfig, webSearchEnabled: boolean): void;
24
+ /**
25
+ * pi-web-ui 的就绪检测只认 <agentDir>/auth.json 里有没有凭据条目(models.json
26
+ * 的 apiKey 它不看),空 {} 会在网页里弹「未检测到 pi agent 配置」引导装 pi。
27
+ * 这里补一条 u1s1 的 api_key 记录 —— key 用 $U1S1_API_KEY 环境变量引用,
28
+ * pi 的 resolveConfigValue 读取时才解析,文件本身不落密钥,和 models.json 同款。
29
+ * 其他 provider 的凭据原样保留;文件损坏时不动它,交给 SDK 自己报错。
30
+ */
31
+ export declare function ensureAuthCredential(): void;
32
+ export declare function ensureProviderModels(cfg: CliConfig): void;
@@ -106,6 +106,33 @@ export function writeWebToolsExtension(cfg, webSearchEnabled) {
106
106
  ` pi.registerTool(tools.webFetchTool);\n` +
107
107
  `}\n`);
108
108
  }
109
+ /**
110
+ * pi-web-ui 的就绪检测只认 <agentDir>/auth.json 里有没有凭据条目(models.json
111
+ * 的 apiKey 它不看),空 {} 会在网页里弹「未检测到 pi agent 配置」引导装 pi。
112
+ * 这里补一条 u1s1 的 api_key 记录 —— key 用 $U1S1_API_KEY 环境变量引用,
113
+ * pi 的 resolveConfigValue 读取时才解析,文件本身不落密钥,和 models.json 同款。
114
+ * 其他 provider 的凭据原样保留;文件损坏时不动它,交给 SDK 自己报错。
115
+ */
116
+ export function ensureAuthCredential() {
117
+ mkdirSync(agentDir, { recursive: true });
118
+ const p = join(agentDir, "auth.json");
119
+ let root = {};
120
+ if (existsSync(p)) {
121
+ try {
122
+ root = JSON.parse(readFileSync(p, "utf8"));
123
+ }
124
+ catch {
125
+ return;
126
+ }
127
+ if (typeof root !== "object" || root === null || Array.isArray(root))
128
+ return;
129
+ }
130
+ const entry = root[PROVIDER_ID];
131
+ if (entry?.["type"] === "api_key" && entry["key"] === "$U1S1_API_KEY")
132
+ return;
133
+ root[PROVIDER_ID] = { type: "api_key", key: "$U1S1_API_KEY" };
134
+ writeFileSync(p, JSON.stringify(root, null, 2) + "\n", { mode: 0o600 });
135
+ }
109
136
  export function ensureProviderModels(cfg) {
110
137
  mkdirSync(agentDir, { recursive: true });
111
138
  const p = join(agentDir, "models.json");
package/dist/api.d.ts ADDED
@@ -0,0 +1,51 @@
1
+ import { type CliConfig } from "./config.js";
2
+ export interface MeResponse {
3
+ email: string | null;
4
+ signup_credit_usd?: number;
5
+ monthly_free_usd?: number;
6
+ daily_free_usd: number;
7
+ daily_free_used_usd: number;
8
+ daily_free_remaining_usd: number;
9
+ daily_free_resets_at: string;
10
+ daily_free_model: string;
11
+ mtd_usd: number;
12
+ balance_spent_usd: number;
13
+ bonus_balance_usd: number;
14
+ remaining_usd: number;
15
+ /** USD→默认模型 Token 折算率;老网关没有该字段,展示时需兜底回 $ */
16
+ tokens_per_usd?: number;
17
+ }
18
+ export interface ApiModel {
19
+ id: string;
20
+ name: string;
21
+ reasoning: boolean;
22
+ context_length: number;
23
+ max_tokens: number;
24
+ price: {
25
+ input: number;
26
+ output: number;
27
+ cache_read: number | null;
28
+ };
29
+ }
30
+ /** 服务端能力开关;字段缺失(老网关)时按开启处理,保持现状。 */
31
+ export interface ApiFeatures {
32
+ web_search?: boolean;
33
+ }
34
+ export interface ModelsResponse {
35
+ models: ApiModel[];
36
+ features: ApiFeatures;
37
+ }
38
+ export declare function fetchModels(cfg: CliConfig): Promise<ModelsResponse>;
39
+ export interface SearchResult {
40
+ title: string;
41
+ url: string;
42
+ snippet: string;
43
+ }
44
+ export interface SearchResponse {
45
+ query: string;
46
+ answer: string | null;
47
+ results: SearchResult[];
48
+ }
49
+ /** 联网搜索走网关代理(上游 key 只在服务端)。maxResults 不传时由服务端决定默认值。 */
50
+ export declare function searchWeb(cfg: Pick<CliConfig, "baseUrl" | "apiKey">, query: string, maxResults?: number, signal?: AbortSignal): Promise<SearchResponse>;
51
+ export declare function fetchMe(cfg: CliConfig): Promise<MeResponse>;
@@ -0,0 +1,13 @@
1
+ import type { Theme } from "@earendil-works/pi-coding-agent";
2
+ export declare const BRAND_NAME = "u1s1";
3
+ export declare const BRAND_CN = "\u6709\u4E00\u8BF4\u4E00";
4
+ export declare const BRAND_TAGLINE = "\u8BF4\u4EBA\u8BDD\u7684 AI \u7F16\u7A0B\u642D\u5B50";
5
+ export declare const DASHBOARD_URL = "https://u1s1.io/dashboard";
6
+ export declare function formatHomePath(path: string): string;
7
+ export declare const HERO_ART: string[];
8
+ /**
9
+ * Startup hero, responsive to terminal width:
10
+ * wide → wordmark with info column beside it; medium → stacked; narrow → one-liner.
11
+ */
12
+ export declare function renderBrandHeader(theme: Theme, version: string, cwd: string, width: number, notice?: string): string[];
13
+ export declare function printConsoleBanner(version: string): void;
@@ -0,0 +1,77 @@
1
+ export declare const VERSION: string;
2
+ /**
3
+ * 便携包安装(install.sh / install.ps1):包根旁边带自己的 node 运行时,
4
+ * npm 更新碰不到这份拷贝,升级只能整包重装。npm 全局安装的包在 node_modules
5
+ * 下,按父目录名先排除,避免撞上恰好叫 node 的目录误判。
6
+ */
7
+ export declare function isPortableInstall(): boolean;
8
+ export declare const DEFAULT_BASE_URL = "https://api.u1s1.io/v1";
9
+ export declare const PROVIDER_ID = "u1s1";
10
+ /**
11
+ * Convert an API model response to our internal ModelDef.
12
+ * Aliases are derived from the short id; note is left empty (filled by /model command).
13
+ */
14
+ export declare function apiModelToDef(m: {
15
+ id: string;
16
+ name: string;
17
+ reasoning: boolean;
18
+ context_length: number;
19
+ max_tokens: number;
20
+ price: {
21
+ input: number;
22
+ output: number;
23
+ cache_read: number | null;
24
+ };
25
+ }): ModelDef;
26
+ export interface ModelDef {
27
+ id: string;
28
+ name: string;
29
+ /** short aliases accepted by `u1s1 model <name>` */
30
+ aliases: string[];
31
+ reasoning: boolean;
32
+ contextWindow: number;
33
+ maxTokens: number;
34
+ /** USD per million tokens — display/estimation only; billing is server-side */
35
+ cost: {
36
+ input: number;
37
+ output: number;
38
+ cacheRead: number;
39
+ cacheWrite: number;
40
+ };
41
+ note: string;
42
+ }
43
+ export declare const MODELS: ModelDef[];
44
+ /** Replace MODELS with a fresh list fetched from server (e.g. at startup). */
45
+ export declare function setModelsFromApi(apiModels: ModelDef[]): void;
46
+ export declare const DEFAULT_MODEL_ID: string;
47
+ export declare function resolveModel(nameOrAlias: string): ModelDef | undefined;
48
+ export interface CliConfig {
49
+ apiKey?: string;
50
+ baseUrl: string;
51
+ /** preferred model id; must be one of MODELS */
52
+ model?: string;
53
+ }
54
+ export declare const u1s1Dir: string;
55
+ /** pi keeps auth/models/settings/sessions under this dir — isolated from any real pi install. */
56
+ export declare const agentDir: string;
57
+ export declare const agentSettingsFile: string;
58
+ export interface AgentSettings {
59
+ showStartupBanner?: boolean;
60
+ autoUpdate?: boolean;
61
+ [key: string]: unknown;
62
+ }
63
+ /** Read u1s1 agent settings (settings.json). Returns empty object if missing or invalid. */
64
+ export declare function readSettings(): AgentSettings;
65
+ /** Model last chosen in-session via /model (pi writes this). */
66
+ export declare function readAgentDefaultModel(): string | undefined;
67
+ /** Keep pi's settings.json in sync so /model and `u1s1 model` share one default. */
68
+ export declare function writeAgentDefaultModel(modelId: string): void;
69
+ /**
70
+ * After this fix both stores stay in sync. If they still disagree (old installs),
71
+ * prefer the in-session /model value — that's the one users thought they had set.
72
+ */
73
+ export declare function resolvePreferredModel(configModel: string | undefined): string;
74
+ export declare function loadConfig(): CliConfig;
75
+ export declare function saveConfig(cfg: CliConfig): void;
76
+ /** Persist the user's preferred model to both stores. */
77
+ export declare function persistPreferredModel(cfg: CliConfig, modelId: string): CliConfig;
@@ -0,0 +1,2 @@
1
+ import { type CliConfig } from "./config.js";
2
+ export declare function deployCommand(cfg: CliConfig, args: string[]): Promise<void>;
package/dist/deploy.js ADDED
@@ -0,0 +1,235 @@
1
+ import { existsSync, readdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
2
+ import { basename, join, relative, resolve, sep } from "node:path";
3
+ import { createInterface } from "node:readline/promises";
4
+ import { VERSION, u1s1Dir } from "./config.js";
5
+ /**
6
+ * u1s1 deploy:把静态网页一键发布到 <name>.u1s1.app。
7
+ * 检测项目里的静态站点根目录 → 首次询问子域名(记在 ~/.u1s1/deploys.json)
8
+ * → 并发上传 → 网关原子切换生效,输出可分享的网址。
9
+ */
10
+ const deploysFile = join(u1s1Dir, "deploys.json");
11
+ /** 构建产物目录优先:Vite/Next 等项目根的 index.html 是源码,不是能直接上线的产物。 */
12
+ const BUILD_DIRS = ["dist", "build", "out", "_site", "public"];
13
+ const SKIP_DIRS = new Set(["node_modules", "__pycache__"]);
14
+ function authHeaders(apiKey) {
15
+ return { authorization: `Bearer ${apiKey}`, "x-u1s1-version": VERSION };
16
+ }
17
+ function readDeploys() {
18
+ try {
19
+ return JSON.parse(readFileSync(deploysFile, "utf8"));
20
+ }
21
+ catch {
22
+ return {};
23
+ }
24
+ }
25
+ function rememberSite(dir, site) {
26
+ const all = readDeploys();
27
+ all[dir] = site;
28
+ writeFileSync(deploysFile, JSON.stringify(all, null, 2) + "\n");
29
+ }
30
+ /** 找要部署的目录:显式参数 > 含 index.html 的构建产物目录 > 当前目录本身。 */
31
+ function resolveSiteDir(explicit) {
32
+ if (explicit) {
33
+ const dir = resolve(explicit);
34
+ if (!existsSync(dir) || !statSync(dir).isDirectory()) {
35
+ throw new Error(`目录不存在:${dir}`);
36
+ }
37
+ if (!existsSync(join(dir, "index.html"))) {
38
+ throw new Error(`${dir} 里没有 index.html,网站需要一个首页`);
39
+ }
40
+ return dir;
41
+ }
42
+ const cwd = process.cwd();
43
+ for (const sub of BUILD_DIRS) {
44
+ if (existsSync(join(cwd, sub, "index.html")))
45
+ return join(cwd, sub);
46
+ }
47
+ if (existsSync(join(cwd, "index.html")))
48
+ return cwd;
49
+ throw new Error("这里找不到能发布的网页(index.html)。\n" +
50
+ " 在网站目录里运行 u1s1 deploy,或指定目录:u1s1 deploy <目录>\n" +
51
+ " 如果项目需要构建(如 Vite/Next),先跑构建再部署 dist/ 等产物目录");
52
+ }
53
+ function collectFiles(root) {
54
+ const files = [];
55
+ const walk = (dir) => {
56
+ for (const name of readdirSync(dir)) {
57
+ if (name.startsWith(".") || SKIP_DIRS.has(name))
58
+ continue;
59
+ const abs = join(dir, name);
60
+ const st = statSync(abs);
61
+ if (st.isDirectory())
62
+ walk(abs);
63
+ else if (st.isFile()) {
64
+ files.push({ path: relative(root, abs).split(sep).join("/"), abs, bytes: st.size });
65
+ }
66
+ }
67
+ };
68
+ walk(root);
69
+ return files;
70
+ }
71
+ /** 从目录名生成默认子域名。 */
72
+ function slugify(name) {
73
+ const slug = name
74
+ .toLowerCase()
75
+ .replace(/[^a-z0-9-]+/g, "-")
76
+ .replace(/-{2,}/g, "-")
77
+ .replace(/^-+|-+$/g, "")
78
+ .slice(0, 30)
79
+ .replace(/^-+|-+$/g, "");
80
+ return slug.length >= 3 ? slug : `site-${Math.random().toString(36).slice(2, 6)}`;
81
+ }
82
+ function fmtBytes(n) {
83
+ if (n >= 1024 * 1024)
84
+ return `${(n / 1024 / 1024).toFixed(1)}MB`;
85
+ if (n >= 1024)
86
+ return `${Math.round(n / 1024)}KB`;
87
+ return `${n}B`;
88
+ }
89
+ async function api(cfg, method, path, body) {
90
+ let resp;
91
+ try {
92
+ resp = await fetch(`${cfg.baseUrl}${path}`, {
93
+ method,
94
+ headers: { ...authHeaders(cfg.apiKey), "content-type": "application/json" },
95
+ body: body === undefined ? undefined : JSON.stringify(body),
96
+ });
97
+ }
98
+ catch {
99
+ throw new Error(`连不上 ${cfg.baseUrl},检查一下网络?`);
100
+ }
101
+ const data = (await resp.json().catch(() => null));
102
+ if (!resp.ok) {
103
+ const e = new Error(data?.error?.message ?? `服务端返回 ${resp.status},稍后再试`);
104
+ e.code = data?.error?.code;
105
+ throw e;
106
+ }
107
+ return data;
108
+ }
109
+ /** 逐个上传,失败重试一次;并发数保守取 6。 */
110
+ async function uploadAll(cfg, start, files) {
111
+ let done = 0;
112
+ const queue = [...files];
113
+ const uploadOne = async (f) => {
114
+ const qs = new URLSearchParams({ site: start.site, deploy_id: start.deploy_id, path: f.path });
115
+ const put = async () => fetch(`${cfg.baseUrl}/deploy/file?${qs}`, {
116
+ method: "PUT",
117
+ headers: { ...authHeaders(cfg.apiKey), "content-type": "application/octet-stream" },
118
+ body: readFileSync(f.abs),
119
+ });
120
+ let resp = await put().catch(() => null);
121
+ if (!resp?.ok)
122
+ resp = await put().catch(() => null);
123
+ if (!resp?.ok) {
124
+ const body = resp ? (await resp.json().catch(() => null)) : null;
125
+ throw new Error(`上传 ${f.path} 失败:${body?.error?.message ?? "网络错误"}`);
126
+ }
127
+ done++;
128
+ process.stdout.write(`\r 上传中 ${done}/${files.length} ${f.path.slice(0, 48).padEnd(48)}`);
129
+ };
130
+ const workers = Array.from({ length: Math.min(6, queue.length) }, async () => {
131
+ for (let f = queue.shift(); f; f = queue.shift())
132
+ await uploadOne(f);
133
+ });
134
+ await Promise.all(workers);
135
+ process.stdout.write("\r" + " ".repeat(70) + "\r");
136
+ }
137
+ async function promptSiteName(def) {
138
+ if (!process.stdin.isTTY)
139
+ return def;
140
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
141
+ try {
142
+ const answer = (await rl.question(` 站点子域名(回车用 ${def}):`)).trim().toLowerCase();
143
+ return answer || def;
144
+ }
145
+ finally {
146
+ rl.close();
147
+ }
148
+ }
149
+ export async function deployCommand(cfg, args) {
150
+ // 参数:[dir] [--name xxx];u1s1 deploy list 列出已有站点
151
+ if (args[0] === "list") {
152
+ const { sites } = await api(cfg, "GET", "/deploy/sites");
153
+ if (!sites.length) {
154
+ console.log(" 还没有部署过站点。在网页目录里跑 u1s1 deploy 试试。");
155
+ return;
156
+ }
157
+ console.log("");
158
+ for (const s of sites) {
159
+ console.log(` ${s.deployed ? "●" : "○"} ${s.url} ${fmtBytes(s.total_bytes)} · ${s.updated_at} UTC`);
160
+ }
161
+ console.log("");
162
+ return;
163
+ }
164
+ let name;
165
+ let dirArg;
166
+ for (let i = 0; i < args.length; i++) {
167
+ const a = args[i];
168
+ if (a === "--name" || a === "-n")
169
+ name = args[++i]?.toLowerCase();
170
+ else if (a.startsWith("--name="))
171
+ name = a.slice(7).toLowerCase();
172
+ else if (!a.startsWith("-"))
173
+ dirArg = a;
174
+ }
175
+ const dir = resolveSiteDir(dirArg);
176
+ const files = collectFiles(dir);
177
+ if (!files.some((f) => f.path === "index.html")) {
178
+ throw new Error(`${dir} 里没有 index.html,网站需要一个首页`);
179
+ }
180
+ const totalBytes = files.reduce((s, f) => s + f.bytes, 0);
181
+ console.log("");
182
+ console.log(` 部署目录 ${dir}`);
183
+ console.log(` 文件 ${files.length} 个,共 ${fmtBytes(totalBytes)}`);
184
+ // 站点名:--name > 上次用过的 > 交互询问(默认目录名;dist 等产物目录用项目名)
185
+ const remembered = readDeploys()[dir];
186
+ if (!name && remembered)
187
+ name = remembered;
188
+ if (!name) {
189
+ const projectName = BUILD_DIRS.includes(basename(dir)) ? basename(resolve(dir, "..")) : basename(dir);
190
+ name = await promptSiteName(slugify(projectName));
191
+ }
192
+ let start;
193
+ for (let attempt = 0; !start; attempt++) {
194
+ try {
195
+ start = await api(cfg, "POST", "/deploy/start", { site: name });
196
+ }
197
+ catch (e) {
198
+ const code = e.code;
199
+ const retriable = code === "site_name_taken" || code === "invalid_site_name";
200
+ if (!retriable || attempt >= 3)
201
+ throw e;
202
+ console.log(` ${e.message}`);
203
+ if (!process.stdin.isTTY) {
204
+ // 非交互环境自动加后缀重试一次
205
+ if (attempt > 0)
206
+ throw e;
207
+ name = `${name.slice(0, 25)}-${Math.random().toString(36).slice(2, 6)}`;
208
+ }
209
+ else {
210
+ name = await promptSiteName(`${name.slice(0, 25)}-${Math.random().toString(36).slice(2, 6)}`);
211
+ }
212
+ }
213
+ }
214
+ const tooBig = files.filter((f) => f.bytes > start.limits.max_file_bytes);
215
+ if (tooBig.length) {
216
+ throw new Error(`这些文件超过单文件上限 ${fmtBytes(start.limits.max_file_bytes)}:\n` +
217
+ tooBig.map((f) => ` ${f.path}(${fmtBytes(f.bytes)})`).join("\n"));
218
+ }
219
+ if (files.length > start.limits.max_files || totalBytes > start.limits.max_total_bytes) {
220
+ throw new Error(`超出配额:最多 ${start.limits.max_files} 个文件 / ${fmtBytes(start.limits.max_total_bytes)}。` +
221
+ `当前 ${files.length} 个 / ${fmtBytes(totalBytes)}`);
222
+ }
223
+ await uploadAll(cfg, start, files);
224
+ const fin = await api(cfg, "POST", "/deploy/finish", {
225
+ site: start.site,
226
+ deploy_id: start.deploy_id,
227
+ });
228
+ rememberSite(dir, start.site);
229
+ console.log(` ✅ 部署完成,${fin.file_count} 个文件已上线`);
230
+ console.log("");
231
+ console.log(` 🌐 ${fin.url}`);
232
+ console.log("");
233
+ console.log(" 把网址发给朋友就能看。改完代码再跑一次 u1s1 deploy 即可更新。");
234
+ console.log("");
235
+ }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * 桌面版 App(packages/app)的嵌入 API,对外路径 "u1s1-cli/embed"(见
3
+ * package.json exports)。App 与 CLI 共享 ~/.u1s1 的登录态和 agentDir 配置,
4
+ * 这里只做转发,不放实现 —— 保证两个入口永远走同一份逻辑。
5
+ */
6
+ export { agentDir, loadConfig, saveConfig, u1s1Dir, VERSION, type CliConfig, } from "./config.js";
7
+ export { fetchMe, fetchModels } from "./api.js";
8
+ export { apiOrigin, pollDeviceLogin, startDeviceLogin, type DeviceStart } from "./login.js";
9
+ export { prepareWebEnv } from "./web.js";
10
+ export { applyWebUiBranding, applyWebUiFrontend } from "./webui-brand.js";
11
+ export { DASHBOARD_URL } from "./brand.js";
package/dist/embed.js ADDED
@@ -0,0 +1,11 @@
1
+ /**
2
+ * 桌面版 App(packages/app)的嵌入 API,对外路径 "u1s1-cli/embed"(见
3
+ * package.json exports)。App 与 CLI 共享 ~/.u1s1 的登录态和 agentDir 配置,
4
+ * 这里只做转发,不放实现 —— 保证两个入口永远走同一份逻辑。
5
+ */
6
+ export { agentDir, loadConfig, saveConfig, u1s1Dir, VERSION, } from "./config.js";
7
+ export { fetchMe, fetchModels } from "./api.js";
8
+ export { apiOrigin, pollDeviceLogin, startDeviceLogin } from "./login.js";
9
+ export { prepareWebEnv } from "./web.js";
10
+ export { applyWebUiBranding, applyWebUiFrontend } from "./webui-brand.js";
11
+ export { DASHBOARD_URL } from "./brand.js";
@@ -0,0 +1,3 @@
1
+ import type { SourceAdapter, SourceSession } from "./types.js";
2
+ export declare function hydrateClaudeSession(session: SourceSession): Promise<SourceSession>;
3
+ export declare const claudeAdapter: SourceAdapter;
@@ -0,0 +1,3 @@
1
+ import type { SourceAdapter, SourceSession } from "./types.js";
2
+ export declare function hydrateCodexSession(session: SourceSession): Promise<SourceSession>;
3
+ export declare const codexAdapter: SourceAdapter;
@@ -0,0 +1 @@
1
+ export declare function importCommand(args: string[]): Promise<void>;
@@ -0,0 +1,104 @@
1
+ export type ImportSourceId = "claude" | "codex";
2
+ export interface SourceSession {
3
+ source: ImportSourceId;
4
+ /** Stable id from the original tool (session uuid). */
5
+ sourceId: string;
6
+ sourcePath: string;
7
+ cwd: string;
8
+ title?: string;
9
+ startedAt?: number;
10
+ mtimeMs: number;
11
+ }
12
+ export interface DiscoverOpts {
13
+ /** If set, only sessions that belong to this working directory. */
14
+ cwd?: string;
15
+ }
16
+ export type ImportedContent = {
17
+ type: "text";
18
+ text: string;
19
+ } | {
20
+ type: "thinking";
21
+ thinking: string;
22
+ } | {
23
+ type: "toolCall";
24
+ id: string;
25
+ name: string;
26
+ arguments: Record<string, unknown>;
27
+ };
28
+ export interface ImportedUser {
29
+ role: "user";
30
+ text: string;
31
+ timestamp: number;
32
+ }
33
+ export interface ImportedAssistant {
34
+ role: "assistant";
35
+ content: ImportedContent[];
36
+ provider: string;
37
+ model: string;
38
+ api: string;
39
+ stopReason: "stop" | "length" | "toolUse" | "error" | "aborted";
40
+ usage?: {
41
+ input: number;
42
+ output: number;
43
+ cacheRead: number;
44
+ cacheWrite: number;
45
+ };
46
+ timestamp: number;
47
+ }
48
+ export interface ImportedToolResult {
49
+ role: "toolResult";
50
+ toolCallId: string;
51
+ toolName: string;
52
+ text: string;
53
+ isError: boolean;
54
+ timestamp: number;
55
+ }
56
+ export type ImportedMessage = ImportedUser | ImportedAssistant | ImportedToolResult;
57
+ export interface ConvertedSession {
58
+ cwd: string;
59
+ title?: string;
60
+ messages: ImportedMessage[];
61
+ }
62
+ export interface SourceAdapter {
63
+ id: ImportSourceId;
64
+ label: string;
65
+ discover(opts: DiscoverOpts): SourceSession[];
66
+ convert(session: SourceSession): ConvertedSession;
67
+ }
68
+ export interface ImportRecord {
69
+ source: ImportSourceId;
70
+ sourceId: string;
71
+ sourcePath: string;
72
+ destPath: string;
73
+ cwd: string;
74
+ title?: string;
75
+ importedAt: string;
76
+ }
77
+ export interface ImportIndex {
78
+ version: 1;
79
+ items: Record<string, ImportRecord>;
80
+ }
81
+ export interface ImportOptions {
82
+ sources: ImportSourceId[];
83
+ /** Only this project. Omit to import every discovered session. */
84
+ cwd?: string;
85
+ dryRun: boolean;
86
+ force: boolean;
87
+ limit?: number;
88
+ }
89
+ export interface ImportResultItem {
90
+ source: ImportSourceId;
91
+ sourceId: string;
92
+ title: string;
93
+ cwd: string;
94
+ destPath?: string;
95
+ status: "imported" | "skipped" | "empty" | "error";
96
+ detail?: string;
97
+ }
98
+ export interface ImportSummary {
99
+ items: ImportResultItem[];
100
+ imported: number;
101
+ skipped: number;
102
+ empty: number;
103
+ errors: number;
104
+ }
@@ -0,0 +1,26 @@
1
+ export declare const MAX_TOOL_RESULT_CHARS = 80000;
2
+ export declare const MAX_TEXT_CHARS = 200000;
3
+ export declare const PREVIEW_TITLE_CHARS = 48;
4
+ export declare function resolveExistingDir(path: string): string | undefined;
5
+ export declare function listHomeClaudeDirs(): string[];
6
+ export declare function uniqueExistingDirs(paths: Array<string | undefined>): string[];
7
+ export declare function encodeClaudeProjectDir(cwd: string): string;
8
+ export declare function samePath(a: string, b: string): boolean;
9
+ /** Nearest git root at or above cwd. Stops at home so `~/` is never treated as a mega-project. */
10
+ export declare function projectRoot(cwd: string): string | undefined;
11
+ /** cwd + ancestors up to the git root (or just cwd when there is no repo). */
12
+ export declare function projectAncestors(cwd: string): string[];
13
+ /** Same folder, anywhere inside this git repo, or the repo root when you're in a subfolder. */
14
+ export declare function sessionBelongsToCwd(sessionCwd: string, wanted: string): boolean;
15
+ export declare function parseJsonLine(line: string): Record<string, unknown> | undefined;
16
+ export declare function asRecord(value: unknown): Record<string, unknown> | undefined;
17
+ export declare function asString(value: unknown): string | undefined;
18
+ export declare function parseTime(value: unknown): number | undefined;
19
+ export declare function truncateText(text: string, max: number): string;
20
+ export declare function formatBytes(n: number): string;
21
+ export declare function oneLine(text: string, max?: number): string;
22
+ export declare function firstMeaningfulLine(text: string): string;
23
+ export declare function parseArgsJson(raw: string): Record<string, unknown>;
24
+ export declare function fileMtimeMs(path: string): number;
25
+ export declare function isProbablyInjection(text: string): boolean;
26
+ export declare function readFirstJsonObject(path: string): Record<string, unknown> | undefined;
@@ -0,0 +1,4 @@
1
+ import type { ConvertedSession } from "./types.js";
2
+ export declare function writeConvertedSession(converted: ConvertedSession, destCwd?: string): string;
3
+ export declare function readJsonIfExists(path: string): unknown;
4
+ export declare function writeJson(path: string, value: unknown): void;
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js CHANGED
@@ -5,6 +5,7 @@ import { cleanupBrandThemes, ensureBrandPrompt, ensureDefaultSettings, ensurePro
5
5
  import { printConsoleBanner } from "./brand.js";
6
6
  import { agentDir, apiModelToDef, isPortableInstall, loadConfig, MODELS, persistPreferredModel, PROVIDER_ID, readSettings, resolvePreferredModel, setModelsFromApi, VERSION, } from "./config.js";
7
7
  import { applyBrandUi, setUpdateNotice } from "./style.js";
8
+ import { offerStarterTemplates } from "./templates.js";
8
9
  import { fetchModels } from "./api.js";
9
10
  const PACKAGE_NAME = "u1s1-cli";
10
11
  /**
@@ -167,6 +168,7 @@ async function runAgent(cfg, args) {
167
168
  name: "u1s1",
168
169
  factory: (pi) => {
169
170
  applyBrandUi(pi, VERSION);
171
+ offerStarterTemplates(pi);
170
172
  // pi 内置只有 /quit;补一个更常见的 /exit,方便新手退出
171
173
  pi.registerCommand("exit", {
172
174
  description: "退出 u1s1",
@@ -241,7 +243,7 @@ async function run() {
241
243
  }
242
244
  if (cmd === "--help" || cmd === "-h") {
243
245
  printConsoleBanner(VERSION);
244
- console.log(" u1s1 命令:web(浏览器网页版)· web shortcut(桌面图标)· login / logout · model · usage · update · import");
246
+ console.log(" u1s1 命令:web(浏览器网页版)· web shortcut(桌面图标)· deploy(发布网页)· login / logout · model · usage · update · import");
245
247
  console.log("");
246
248
  }
247
249
  if (cmd === "web") {
@@ -257,6 +259,13 @@ async function run() {
257
259
  await webCommand(cfg, args.slice(1));
258
260
  return;
259
261
  }
262
+ if (cmd === "deploy") {
263
+ const { ensureAuth } = await import("./login.js");
264
+ const cfg = await ensureAuth();
265
+ const { deployCommand } = await import("./deploy.js");
266
+ await deployCommand(cfg, args.slice(1));
267
+ return;
268
+ }
260
269
  if (cmd === "login") {
261
270
  const { login } = await import("./login.js");
262
271
  await login(args[1]);