laysoai 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/LICENSE +21 -0
- package/README.md +262 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +814 -0
- package/dist/index.js.map +1 -0
- package/package.json +48 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 LaysoAI
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
# laysoai
|
|
2
|
+
|
|
3
|
+
`laysoai` is a cross-platform CLI scaffold for configuring Claude Code, Codex CLI, and Gemini CLI to use LaysoAI.
|
|
4
|
+
|
|
5
|
+
Default request URL:
|
|
6
|
+
|
|
7
|
+
```text
|
|
8
|
+
https://laysoai.com
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
The tool does not append `/v1` or provider-specific paths.
|
|
12
|
+
|
|
13
|
+
## Install
|
|
14
|
+
|
|
15
|
+
Run directly:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npx laysoai
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Or install globally:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npm install -g laysoai
|
|
25
|
+
laysoai
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Usage
|
|
29
|
+
|
|
30
|
+
Interactive setup:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
laysoai
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Non-interactive setup:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
laysoai init --key sk-layso-xxx --targets claude,codex,gemini --yes
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Supported targets:
|
|
43
|
+
|
|
44
|
+
```text
|
|
45
|
+
claude
|
|
46
|
+
codex
|
|
47
|
+
gemini
|
|
48
|
+
all
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Preview changes without writing files:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
laysoai init --key sk-layso-xxx --targets all --dry-run
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
During setup, `laysoai` checks:
|
|
58
|
+
|
|
59
|
+
- Whether the selected CLI command is installed
|
|
60
|
+
- Whether its user-level configuration file exists
|
|
61
|
+
- Whether the configuration already points to LaysoAI
|
|
62
|
+
|
|
63
|
+
Check current configuration:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
laysoai config
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Check installed CLI tools and configuration status:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
laysoai doctor
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
`doctor` is the quickest way to inspect CLI installation and config file status before writing changes.
|
|
76
|
+
|
|
77
|
+
Restore the latest backup generated by `laysoai`:
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
laysoai reset --targets all
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## What It Writes
|
|
84
|
+
|
|
85
|
+
`laysoai` directly modifies local user-level CLI configuration files. Before writing, it creates a backup under:
|
|
86
|
+
|
|
87
|
+
```text
|
|
88
|
+
~/.laysoai/backups
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
It also records backup metadata in:
|
|
92
|
+
|
|
93
|
+
```text
|
|
94
|
+
~/.laysoai/manifest.json
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Claude Code
|
|
98
|
+
|
|
99
|
+
Writes:
|
|
100
|
+
|
|
101
|
+
```text
|
|
102
|
+
~/.claude/settings.json
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Values:
|
|
106
|
+
|
|
107
|
+
```json
|
|
108
|
+
{
|
|
109
|
+
"env": {
|
|
110
|
+
"ANTHROPIC_BASE_URL": "https://laysoai.com",
|
|
111
|
+
"ANTHROPIC_AUTH_TOKEN": "your-key",
|
|
112
|
+
"ANTHROPIC_API_KEY": "your-key"
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Codex CLI
|
|
118
|
+
|
|
119
|
+
Writes:
|
|
120
|
+
|
|
121
|
+
```text
|
|
122
|
+
~/.codex/config.toml
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Values:
|
|
126
|
+
|
|
127
|
+
```toml
|
|
128
|
+
model_provider = "laysoai"
|
|
129
|
+
|
|
130
|
+
[model_providers.laysoai]
|
|
131
|
+
name = "LaysoAI"
|
|
132
|
+
base_url = "https://laysoai.com"
|
|
133
|
+
wire_api = "responses"
|
|
134
|
+
experimental_bearer_token = "your-key"
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### Gemini CLI
|
|
138
|
+
|
|
139
|
+
Writes:
|
|
140
|
+
|
|
141
|
+
```text
|
|
142
|
+
~/.gemini/.env
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Values:
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
GEMINI_API_KEY="your-key"
|
|
149
|
+
GOOGLE_GEMINI_BASE_URL="https://laysoai.com"
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
## Platform Support
|
|
153
|
+
|
|
154
|
+
The CLI is designed for:
|
|
155
|
+
|
|
156
|
+
- Windows
|
|
157
|
+
- macOS
|
|
158
|
+
- Linux
|
|
159
|
+
|
|
160
|
+
It uses Node.js `os.homedir()` and `path.join()` for user-level paths instead of hard-coded system paths.
|
|
161
|
+
|
|
162
|
+
## Development
|
|
163
|
+
|
|
164
|
+
Install dependencies:
|
|
165
|
+
|
|
166
|
+
```bash
|
|
167
|
+
npm install
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
Run locally:
|
|
171
|
+
|
|
172
|
+
```bash
|
|
173
|
+
npm run dev -- --help
|
|
174
|
+
npm run dev -- init --dry-run
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
Build:
|
|
178
|
+
|
|
179
|
+
```bash
|
|
180
|
+
npm run build
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
Run checks:
|
|
184
|
+
|
|
185
|
+
```bash
|
|
186
|
+
npm run typecheck
|
|
187
|
+
npm run test
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
Test package locally:
|
|
191
|
+
|
|
192
|
+
```bash
|
|
193
|
+
npm pack
|
|
194
|
+
npm install -g ./laysoai-0.1.0.tgz
|
|
195
|
+
laysoai --help
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
## Publish to npm
|
|
199
|
+
|
|
200
|
+
1. Create or log in to an npm account:
|
|
201
|
+
|
|
202
|
+
```bash
|
|
203
|
+
npm login
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
2. Confirm the package name is available:
|
|
207
|
+
|
|
208
|
+
```bash
|
|
209
|
+
npm view laysoai
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
If npm returns `404 Not Found`, the package name is available.
|
|
213
|
+
|
|
214
|
+
3. Run final checks:
|
|
215
|
+
|
|
216
|
+
```bash
|
|
217
|
+
npm run typecheck
|
|
218
|
+
npm run test
|
|
219
|
+
npm run build
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
4. Preview the package:
|
|
223
|
+
|
|
224
|
+
```bash
|
|
225
|
+
npm pack --dry-run
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
5. Publish:
|
|
229
|
+
|
|
230
|
+
```bash
|
|
231
|
+
npm publish --access public
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
6. Verify:
|
|
235
|
+
|
|
236
|
+
```bash
|
|
237
|
+
npm view laysoai version
|
|
238
|
+
npx laysoai --help
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
## Release Updates
|
|
242
|
+
|
|
243
|
+
Patch release:
|
|
244
|
+
|
|
245
|
+
```bash
|
|
246
|
+
npm version patch
|
|
247
|
+
npm publish --access public
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
Minor release:
|
|
251
|
+
|
|
252
|
+
```bash
|
|
253
|
+
npm version minor
|
|
254
|
+
npm publish --access public
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
Major release:
|
|
258
|
+
|
|
259
|
+
```bash
|
|
260
|
+
npm version major
|
|
261
|
+
npm publish --access public
|
|
262
|
+
```
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,814 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/cli.ts
|
|
4
|
+
import { checkbox, confirm, password } from "@inquirer/prompts";
|
|
5
|
+
import chalk2 from "chalk";
|
|
6
|
+
import { Command } from "commander";
|
|
7
|
+
import ora from "ora";
|
|
8
|
+
|
|
9
|
+
// src/constants.ts
|
|
10
|
+
var PACKAGE_NAME = "laysoai";
|
|
11
|
+
var PACKAGE_VERSION = "0.1.0";
|
|
12
|
+
var LAYSOAI_BASE_URL = "https://laysoai.com";
|
|
13
|
+
var LAYSOAI_HOME_DIR = ".laysoai";
|
|
14
|
+
var TARGETS = ["claude", "codex", "gemini"];
|
|
15
|
+
|
|
16
|
+
// src/adapters/claude.ts
|
|
17
|
+
import path5 from "path";
|
|
18
|
+
|
|
19
|
+
// src/core/backup.ts
|
|
20
|
+
import path3 from "path";
|
|
21
|
+
|
|
22
|
+
// src/core/fs.ts
|
|
23
|
+
import fs from "fs/promises";
|
|
24
|
+
import path from "path";
|
|
25
|
+
async function pathExists(filePath) {
|
|
26
|
+
try {
|
|
27
|
+
await fs.access(filePath);
|
|
28
|
+
return true;
|
|
29
|
+
} catch {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
async function ensureDir(dirPath) {
|
|
34
|
+
await fs.mkdir(dirPath, { recursive: true });
|
|
35
|
+
}
|
|
36
|
+
async function readText(filePath) {
|
|
37
|
+
if (!await pathExists(filePath)) {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
return fs.readFile(filePath, "utf8");
|
|
41
|
+
}
|
|
42
|
+
async function writeText(filePath, content) {
|
|
43
|
+
await ensureDir(path.dirname(filePath));
|
|
44
|
+
await fs.writeFile(filePath, content, "utf8");
|
|
45
|
+
}
|
|
46
|
+
async function readJsonObject(filePath) {
|
|
47
|
+
const content = await readText(filePath);
|
|
48
|
+
if (!content || !content.trim()) {
|
|
49
|
+
return {};
|
|
50
|
+
}
|
|
51
|
+
const parsed = JSON.parse(content);
|
|
52
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
53
|
+
throw new Error(`${filePath} must contain a JSON object.`);
|
|
54
|
+
}
|
|
55
|
+
return parsed;
|
|
56
|
+
}
|
|
57
|
+
async function writeJsonObject(filePath, value) {
|
|
58
|
+
await writeText(filePath, `${JSON.stringify(value, null, 2)}
|
|
59
|
+
`);
|
|
60
|
+
}
|
|
61
|
+
async function copyFileIfExists(source, destination) {
|
|
62
|
+
if (!await pathExists(source)) {
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
await ensureDir(path.dirname(destination));
|
|
66
|
+
await fs.copyFile(source, destination);
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// src/core/paths.ts
|
|
71
|
+
import os from "os";
|
|
72
|
+
import path2 from "path";
|
|
73
|
+
function homePath(...parts) {
|
|
74
|
+
return path2.join(os.homedir(), ...parts);
|
|
75
|
+
}
|
|
76
|
+
function laysoaiPath(...parts) {
|
|
77
|
+
return homePath(LAYSOAI_HOME_DIR, ...parts);
|
|
78
|
+
}
|
|
79
|
+
function timestampId(date = /* @__PURE__ */ new Date()) {
|
|
80
|
+
return date.toISOString().replace(/[:.]/g, "-");
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// src/core/backup.ts
|
|
84
|
+
async function backupConfig(target, configPath) {
|
|
85
|
+
const id = timestampId();
|
|
86
|
+
const backupPath = laysoaiPath("backups", target, id, path3.basename(configPath));
|
|
87
|
+
const existed = await copyFileIfExists(configPath, backupPath);
|
|
88
|
+
return {
|
|
89
|
+
id,
|
|
90
|
+
backupPath: existed ? backupPath : null,
|
|
91
|
+
existed
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// src/core/commands.ts
|
|
96
|
+
import { spawn } from "child_process";
|
|
97
|
+
import process2 from "process";
|
|
98
|
+
async function commandExists(command) {
|
|
99
|
+
const checker = process2.platform === "win32" ? "where" : "command";
|
|
100
|
+
const args = process2.platform === "win32" ? [command] : ["-v", command];
|
|
101
|
+
return new Promise((resolve) => {
|
|
102
|
+
const child = spawn(checker, args, {
|
|
103
|
+
stdio: "ignore",
|
|
104
|
+
shell: process2.platform !== "win32"
|
|
105
|
+
});
|
|
106
|
+
child.on("error", () => resolve(false));
|
|
107
|
+
child.on("close", (code) => resolve(code === 0));
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// src/core/manifest.ts
|
|
112
|
+
import fs2 from "fs/promises";
|
|
113
|
+
import path4 from "path";
|
|
114
|
+
var MANIFEST_PATH = laysoaiPath("manifest.json");
|
|
115
|
+
async function readManifest() {
|
|
116
|
+
const content = await readText(MANIFEST_PATH);
|
|
117
|
+
if (!content) {
|
|
118
|
+
return { version: 1, entries: [] };
|
|
119
|
+
}
|
|
120
|
+
const parsed = JSON.parse(content);
|
|
121
|
+
return {
|
|
122
|
+
version: 1,
|
|
123
|
+
entries: Array.isArray(parsed.entries) ? parsed.entries : []
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
async function writeManifest(manifest) {
|
|
127
|
+
await ensureDir(path4.dirname(MANIFEST_PATH));
|
|
128
|
+
await writeText(MANIFEST_PATH, `${JSON.stringify(manifest, null, 2)}
|
|
129
|
+
`);
|
|
130
|
+
}
|
|
131
|
+
async function addManifestEntry(entry) {
|
|
132
|
+
const manifest = await readManifest();
|
|
133
|
+
manifest.entries.push(entry);
|
|
134
|
+
await writeManifest(manifest);
|
|
135
|
+
}
|
|
136
|
+
async function latestEntryFor(target) {
|
|
137
|
+
const manifest = await readManifest();
|
|
138
|
+
const matches = manifest.entries.filter((entry) => entry.target === target);
|
|
139
|
+
return matches.at(-1) ?? null;
|
|
140
|
+
}
|
|
141
|
+
async function restoreEntry(entry) {
|
|
142
|
+
if (entry.existed && entry.backupPath) {
|
|
143
|
+
if (!await pathExists(entry.backupPath)) {
|
|
144
|
+
throw new Error(`Backup file not found: ${entry.backupPath}`);
|
|
145
|
+
}
|
|
146
|
+
await ensureDir(path4.dirname(entry.configPath));
|
|
147
|
+
await fs2.copyFile(entry.backupPath, entry.configPath);
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
await fs2.rm(entry.configPath, { force: true });
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// src/core/mask.ts
|
|
154
|
+
function maskSecret(secret) {
|
|
155
|
+
if (secret.length <= 8) {
|
|
156
|
+
return "*".repeat(secret.length);
|
|
157
|
+
}
|
|
158
|
+
return `${secret.slice(0, 4)}...${secret.slice(-4)}`;
|
|
159
|
+
}
|
|
160
|
+
function hasValue(value) {
|
|
161
|
+
return typeof value === "string" && value.trim().length > 0;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// src/adapters/claude.ts
|
|
165
|
+
var ENV_VALUES = (apiKey) => ({
|
|
166
|
+
ANTHROPIC_BASE_URL: LAYSOAI_BASE_URL,
|
|
167
|
+
ANTHROPIC_AUTH_TOKEN: apiKey,
|
|
168
|
+
ANTHROPIC_API_KEY: apiKey
|
|
169
|
+
});
|
|
170
|
+
var claudeAdapter = {
|
|
171
|
+
id: "claude",
|
|
172
|
+
name: "Claude Code",
|
|
173
|
+
command: "claude",
|
|
174
|
+
configPath() {
|
|
175
|
+
return homePath(".claude", "settings.json");
|
|
176
|
+
},
|
|
177
|
+
async detect() {
|
|
178
|
+
return { command: this.command, installed: await commandExists(this.command) };
|
|
179
|
+
},
|
|
180
|
+
async inspect() {
|
|
181
|
+
const configPath = this.configPath();
|
|
182
|
+
const exists = await pathExists(configPath);
|
|
183
|
+
if (!exists) {
|
|
184
|
+
return {
|
|
185
|
+
target: this.id,
|
|
186
|
+
name: this.name,
|
|
187
|
+
configPath,
|
|
188
|
+
exists,
|
|
189
|
+
configured: false,
|
|
190
|
+
summary: "\u914D\u7F6E\u6587\u4EF6\u4E0D\u5B58\u5728"
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
const config = await readJsonObject(configPath);
|
|
194
|
+
const env = asRecord(config.env);
|
|
195
|
+
const configured = env.ANTHROPIC_BASE_URL === LAYSOAI_BASE_URL && (hasValue(env.ANTHROPIC_AUTH_TOKEN) || hasValue(env.ANTHROPIC_API_KEY));
|
|
196
|
+
return {
|
|
197
|
+
target: this.id,
|
|
198
|
+
name: this.name,
|
|
199
|
+
configPath,
|
|
200
|
+
exists,
|
|
201
|
+
configured,
|
|
202
|
+
summary: configured ? "\u5DF2\u914D\u7F6E LaysoAI env" : "\u672A\u68C0\u6D4B\u5230\u5B8C\u6574 LaysoAI env"
|
|
203
|
+
};
|
|
204
|
+
},
|
|
205
|
+
async plan(input) {
|
|
206
|
+
const configPath = this.configPath();
|
|
207
|
+
const before = await this.inspect();
|
|
208
|
+
const config = await safeReadJson(configPath);
|
|
209
|
+
const env = asRecord(config.env);
|
|
210
|
+
const desired = ENV_VALUES(input.apiKey);
|
|
211
|
+
return {
|
|
212
|
+
target: this.id,
|
|
213
|
+
name: this.name,
|
|
214
|
+
configPath,
|
|
215
|
+
backupPath: null,
|
|
216
|
+
beforeSummary: before.summary,
|
|
217
|
+
afterSummary: "\u5199\u5165 ANTHROPIC_BASE_URL\u3001ANTHROPIC_AUTH_TOKEN\u3001ANTHROPIC_API_KEY",
|
|
218
|
+
changes: Object.entries(desired).map(([key, value]) => ({
|
|
219
|
+
key,
|
|
220
|
+
action: env[key] === value ? "skip" : key in env ? "update" : "add"
|
|
221
|
+
}))
|
|
222
|
+
};
|
|
223
|
+
},
|
|
224
|
+
async apply(input) {
|
|
225
|
+
const configPath = this.configPath();
|
|
226
|
+
const plan = await this.plan(input);
|
|
227
|
+
const backup = await backupConfig(this.id, configPath);
|
|
228
|
+
try {
|
|
229
|
+
const config = await safeReadJson(configPath);
|
|
230
|
+
const env = asRecord(config.env);
|
|
231
|
+
config.env = {
|
|
232
|
+
...env,
|
|
233
|
+
...ENV_VALUES(input.apiKey)
|
|
234
|
+
};
|
|
235
|
+
await writeJsonObject(configPath, config);
|
|
236
|
+
} catch (error) {
|
|
237
|
+
if (backup.existed && backup.backupPath) {
|
|
238
|
+
const original = await import("fs/promises");
|
|
239
|
+
await original.copyFile(backup.backupPath, configPath);
|
|
240
|
+
}
|
|
241
|
+
throw error;
|
|
242
|
+
}
|
|
243
|
+
await addManifestEntry({
|
|
244
|
+
id: backup.id,
|
|
245
|
+
date: (/* @__PURE__ */ new Date()).toISOString(),
|
|
246
|
+
target: this.id,
|
|
247
|
+
configPath,
|
|
248
|
+
backupPath: backup.backupPath,
|
|
249
|
+
existed: backup.existed
|
|
250
|
+
});
|
|
251
|
+
return { ...plan, backupPath: backup.backupPath };
|
|
252
|
+
},
|
|
253
|
+
verifyHint() {
|
|
254
|
+
return ["claude --version", "claude"];
|
|
255
|
+
}
|
|
256
|
+
};
|
|
257
|
+
async function safeReadJson(configPath) {
|
|
258
|
+
try {
|
|
259
|
+
return await readJsonObject(configPath);
|
|
260
|
+
} catch (error) {
|
|
261
|
+
throw new Error(`\u65E0\u6CD5\u8BFB\u53D6 Claude Code \u914D\u7F6E ${path5.basename(configPath)}\uFF1A${error.message}`);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
function asRecord(value) {
|
|
265
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
266
|
+
return {};
|
|
267
|
+
}
|
|
268
|
+
return value;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// src/adapters/codex.ts
|
|
272
|
+
import * as TOML from "smol-toml";
|
|
273
|
+
var codexAdapter = {
|
|
274
|
+
id: "codex",
|
|
275
|
+
name: "Codex CLI",
|
|
276
|
+
command: "codex",
|
|
277
|
+
configPath() {
|
|
278
|
+
return homePath(".codex", "config.toml");
|
|
279
|
+
},
|
|
280
|
+
async detect() {
|
|
281
|
+
return { command: this.command, installed: await commandExists(this.command) };
|
|
282
|
+
},
|
|
283
|
+
async inspect() {
|
|
284
|
+
const configPath = this.configPath();
|
|
285
|
+
const exists = await pathExists(configPath);
|
|
286
|
+
if (!exists) {
|
|
287
|
+
return {
|
|
288
|
+
target: this.id,
|
|
289
|
+
name: this.name,
|
|
290
|
+
configPath,
|
|
291
|
+
exists,
|
|
292
|
+
configured: false,
|
|
293
|
+
summary: "\u914D\u7F6E\u6587\u4EF6\u4E0D\u5B58\u5728"
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
const config = await readToml(configPath);
|
|
297
|
+
const providers = asRecord2(config.model_providers);
|
|
298
|
+
const laysoai = asRecord2(providers.laysoai);
|
|
299
|
+
const configured = config.model_provider === "laysoai" && laysoai.base_url === LAYSOAI_BASE_URL;
|
|
300
|
+
return {
|
|
301
|
+
target: this.id,
|
|
302
|
+
name: this.name,
|
|
303
|
+
configPath,
|
|
304
|
+
exists,
|
|
305
|
+
configured,
|
|
306
|
+
summary: configured ? "\u5DF2\u914D\u7F6E LaysoAI provider" : "\u672A\u68C0\u6D4B\u5230 LaysoAI provider"
|
|
307
|
+
};
|
|
308
|
+
},
|
|
309
|
+
async plan(input) {
|
|
310
|
+
const configPath = this.configPath();
|
|
311
|
+
const before = await this.inspect();
|
|
312
|
+
const config = await readToml(configPath);
|
|
313
|
+
const providers = asRecord2(config.model_providers);
|
|
314
|
+
const laysoai = asRecord2(providers.laysoai);
|
|
315
|
+
const desired = {
|
|
316
|
+
model_provider: "laysoai",
|
|
317
|
+
"model_providers.laysoai.name": "LaysoAI",
|
|
318
|
+
"model_providers.laysoai.base_url": LAYSOAI_BASE_URL,
|
|
319
|
+
"model_providers.laysoai.wire_api": "responses",
|
|
320
|
+
"model_providers.laysoai.experimental_bearer_token": input.apiKey
|
|
321
|
+
};
|
|
322
|
+
return {
|
|
323
|
+
target: this.id,
|
|
324
|
+
name: this.name,
|
|
325
|
+
configPath,
|
|
326
|
+
backupPath: null,
|
|
327
|
+
beforeSummary: before.summary,
|
|
328
|
+
afterSummary: "\u5199\u5165 Codex LaysoAI provider \u5E76\u8BBE\u4E3A\u9ED8\u8BA4 provider",
|
|
329
|
+
changes: [
|
|
330
|
+
{ key: "model_provider", action: config.model_provider === desired.model_provider ? "skip" : "update" },
|
|
331
|
+
{ key: "model_providers.laysoai.name", action: laysoai.name === desired["model_providers.laysoai.name"] ? "skip" : keyAction(laysoai, "name") },
|
|
332
|
+
{ key: "model_providers.laysoai.base_url", action: laysoai.base_url === desired["model_providers.laysoai.base_url"] ? "skip" : keyAction(laysoai, "base_url") },
|
|
333
|
+
{ key: "model_providers.laysoai.wire_api", action: laysoai.wire_api === desired["model_providers.laysoai.wire_api"] ? "skip" : keyAction(laysoai, "wire_api") },
|
|
334
|
+
{
|
|
335
|
+
key: "model_providers.laysoai.experimental_bearer_token",
|
|
336
|
+
action: laysoai.experimental_bearer_token === input.apiKey ? "skip" : keyAction(laysoai, "experimental_bearer_token")
|
|
337
|
+
}
|
|
338
|
+
]
|
|
339
|
+
};
|
|
340
|
+
},
|
|
341
|
+
async apply(input) {
|
|
342
|
+
const configPath = this.configPath();
|
|
343
|
+
const plan = await this.plan(input);
|
|
344
|
+
const backup = await backupConfig(this.id, configPath);
|
|
345
|
+
try {
|
|
346
|
+
const config = await readToml(configPath);
|
|
347
|
+
const providers = asRecord2(config.model_providers);
|
|
348
|
+
config.model_provider = "laysoai";
|
|
349
|
+
config.model_providers = {
|
|
350
|
+
...providers,
|
|
351
|
+
laysoai: {
|
|
352
|
+
...asRecord2(providers.laysoai),
|
|
353
|
+
name: "LaysoAI",
|
|
354
|
+
base_url: LAYSOAI_BASE_URL,
|
|
355
|
+
wire_api: "responses",
|
|
356
|
+
experimental_bearer_token: input.apiKey
|
|
357
|
+
}
|
|
358
|
+
};
|
|
359
|
+
await writeText(configPath, TOML.stringify(config));
|
|
360
|
+
} catch (error) {
|
|
361
|
+
if (backup.existed && backup.backupPath) {
|
|
362
|
+
const fs3 = await import("fs/promises");
|
|
363
|
+
await fs3.copyFile(backup.backupPath, configPath);
|
|
364
|
+
}
|
|
365
|
+
throw error;
|
|
366
|
+
}
|
|
367
|
+
await addManifestEntry({
|
|
368
|
+
id: backup.id,
|
|
369
|
+
date: (/* @__PURE__ */ new Date()).toISOString(),
|
|
370
|
+
target: this.id,
|
|
371
|
+
configPath,
|
|
372
|
+
backupPath: backup.backupPath,
|
|
373
|
+
existed: backup.existed
|
|
374
|
+
});
|
|
375
|
+
return { ...plan, backupPath: backup.backupPath };
|
|
376
|
+
},
|
|
377
|
+
verifyHint() {
|
|
378
|
+
return ["codex --version", "codex"];
|
|
379
|
+
}
|
|
380
|
+
};
|
|
381
|
+
async function readToml(configPath) {
|
|
382
|
+
const content = await readText(configPath);
|
|
383
|
+
if (!content?.trim()) {
|
|
384
|
+
return {};
|
|
385
|
+
}
|
|
386
|
+
const parsed = TOML.parse(content);
|
|
387
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
388
|
+
throw new Error(`${configPath} must contain a TOML object.`);
|
|
389
|
+
}
|
|
390
|
+
return parsed;
|
|
391
|
+
}
|
|
392
|
+
function asRecord2(value) {
|
|
393
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
394
|
+
return {};
|
|
395
|
+
}
|
|
396
|
+
return value;
|
|
397
|
+
}
|
|
398
|
+
function keyAction(record, key) {
|
|
399
|
+
return key in record ? "update" : "add";
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// src/core/env-file.ts
|
|
403
|
+
function upsertEnvValues(content, values) {
|
|
404
|
+
const lines = content ? content.split(/\r?\n/) : [];
|
|
405
|
+
const seen = /* @__PURE__ */ new Set();
|
|
406
|
+
const nextLines = lines.map((line) => {
|
|
407
|
+
const match = line.match(/^\s*([A-Za-z_][A-Za-z0-9_]*)\s*=/);
|
|
408
|
+
if (!match) {
|
|
409
|
+
return line;
|
|
410
|
+
}
|
|
411
|
+
const key = match[1];
|
|
412
|
+
if (!(key in values)) {
|
|
413
|
+
return line;
|
|
414
|
+
}
|
|
415
|
+
seen.add(key);
|
|
416
|
+
return `${key}=${quoteEnv(values[key])}`;
|
|
417
|
+
});
|
|
418
|
+
for (const [key, value] of Object.entries(values)) {
|
|
419
|
+
if (!seen.has(key)) {
|
|
420
|
+
nextLines.push(`${key}=${quoteEnv(value)}`);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
return `${nextLines.filter((line, index) => line.length > 0 || index < nextLines.length - 1).join("\n")}
|
|
424
|
+
`;
|
|
425
|
+
}
|
|
426
|
+
function quoteEnv(value) {
|
|
427
|
+
return JSON.stringify(value);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// src/adapters/gemini.ts
|
|
431
|
+
var ENV_VALUES2 = (apiKey) => ({
|
|
432
|
+
GEMINI_API_KEY: apiKey,
|
|
433
|
+
GOOGLE_GEMINI_BASE_URL: LAYSOAI_BASE_URL
|
|
434
|
+
});
|
|
435
|
+
var geminiAdapter = {
|
|
436
|
+
id: "gemini",
|
|
437
|
+
name: "Gemini CLI",
|
|
438
|
+
command: "gemini",
|
|
439
|
+
configPath() {
|
|
440
|
+
return homePath(".gemini", ".env");
|
|
441
|
+
},
|
|
442
|
+
async detect() {
|
|
443
|
+
return { command: this.command, installed: await commandExists(this.command) };
|
|
444
|
+
},
|
|
445
|
+
async inspect() {
|
|
446
|
+
const configPath = this.configPath();
|
|
447
|
+
const exists = await pathExists(configPath);
|
|
448
|
+
if (!exists) {
|
|
449
|
+
return {
|
|
450
|
+
target: this.id,
|
|
451
|
+
name: this.name,
|
|
452
|
+
configPath,
|
|
453
|
+
exists,
|
|
454
|
+
configured: false,
|
|
455
|
+
summary: "\u914D\u7F6E\u6587\u4EF6\u4E0D\u5B58\u5728"
|
|
456
|
+
};
|
|
457
|
+
}
|
|
458
|
+
const content = await readText(configPath) ?? "";
|
|
459
|
+
const configured = hasEnvLine(content, "GEMINI_API_KEY") && hasEnvLine(content, "GOOGLE_GEMINI_BASE_URL", LAYSOAI_BASE_URL);
|
|
460
|
+
return {
|
|
461
|
+
target: this.id,
|
|
462
|
+
name: this.name,
|
|
463
|
+
configPath,
|
|
464
|
+
exists,
|
|
465
|
+
configured,
|
|
466
|
+
summary: configured ? "\u5DF2\u914D\u7F6E LaysoAI env" : "\u672A\u68C0\u6D4B\u5230\u5B8C\u6574 LaysoAI env"
|
|
467
|
+
};
|
|
468
|
+
},
|
|
469
|
+
async plan(input) {
|
|
470
|
+
const configPath = this.configPath();
|
|
471
|
+
const before = await this.inspect();
|
|
472
|
+
const content = await readText(configPath) ?? "";
|
|
473
|
+
const desired = ENV_VALUES2(input.apiKey);
|
|
474
|
+
return {
|
|
475
|
+
target: this.id,
|
|
476
|
+
name: this.name,
|
|
477
|
+
configPath,
|
|
478
|
+
backupPath: null,
|
|
479
|
+
beforeSummary: before.summary,
|
|
480
|
+
afterSummary: "\u5199\u5165 GEMINI_API_KEY\u3001GOOGLE_GEMINI_BASE_URL",
|
|
481
|
+
changes: Object.entries(desired).map(([key, value]) => ({
|
|
482
|
+
key,
|
|
483
|
+
action: hasEnvLine(content, key, value) ? "skip" : hasEnvLine(content, key) ? "update" : "add"
|
|
484
|
+
}))
|
|
485
|
+
};
|
|
486
|
+
},
|
|
487
|
+
async apply(input) {
|
|
488
|
+
const configPath = this.configPath();
|
|
489
|
+
const plan = await this.plan(input);
|
|
490
|
+
const backup = await backupConfig(this.id, configPath);
|
|
491
|
+
try {
|
|
492
|
+
const content = await readText(configPath) ?? "";
|
|
493
|
+
await writeText(configPath, upsertEnvValues(content, ENV_VALUES2(input.apiKey)));
|
|
494
|
+
} catch (error) {
|
|
495
|
+
if (backup.existed && backup.backupPath) {
|
|
496
|
+
const fs3 = await import("fs/promises");
|
|
497
|
+
await fs3.copyFile(backup.backupPath, configPath);
|
|
498
|
+
}
|
|
499
|
+
throw error;
|
|
500
|
+
}
|
|
501
|
+
await addManifestEntry({
|
|
502
|
+
id: backup.id,
|
|
503
|
+
date: (/* @__PURE__ */ new Date()).toISOString(),
|
|
504
|
+
target: this.id,
|
|
505
|
+
configPath,
|
|
506
|
+
backupPath: backup.backupPath,
|
|
507
|
+
existed: backup.existed
|
|
508
|
+
});
|
|
509
|
+
return { ...plan, backupPath: backup.backupPath };
|
|
510
|
+
},
|
|
511
|
+
verifyHint() {
|
|
512
|
+
return ["gemini --version", "gemini"];
|
|
513
|
+
}
|
|
514
|
+
};
|
|
515
|
+
function hasEnvLine(content, key, value) {
|
|
516
|
+
const matcher = new RegExp(`^\\s*${escapeRegExp(key)}\\s*=\\s*(.*)\\s*$`, "m");
|
|
517
|
+
const match = content.match(matcher);
|
|
518
|
+
if (!match) {
|
|
519
|
+
return false;
|
|
520
|
+
}
|
|
521
|
+
if (value === void 0) {
|
|
522
|
+
return true;
|
|
523
|
+
}
|
|
524
|
+
return unquote(match[1].trim()) === value;
|
|
525
|
+
}
|
|
526
|
+
function unquote(value) {
|
|
527
|
+
if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
|
|
528
|
+
return value.slice(1, -1);
|
|
529
|
+
}
|
|
530
|
+
return value;
|
|
531
|
+
}
|
|
532
|
+
function escapeRegExp(value) {
|
|
533
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
// src/adapters/index.ts
|
|
537
|
+
var adapters = {
|
|
538
|
+
claude: claudeAdapter,
|
|
539
|
+
codex: codexAdapter,
|
|
540
|
+
gemini: geminiAdapter
|
|
541
|
+
};
|
|
542
|
+
function getAdapters(targets) {
|
|
543
|
+
return targets.map((target) => adapters[target]);
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
// src/core/logger.ts
|
|
547
|
+
import chalk from "chalk";
|
|
548
|
+
var logger = {
|
|
549
|
+
info(message) {
|
|
550
|
+
console.log(chalk.cyan(message));
|
|
551
|
+
},
|
|
552
|
+
success(message) {
|
|
553
|
+
console.log(chalk.green(message));
|
|
554
|
+
},
|
|
555
|
+
warn(message) {
|
|
556
|
+
console.log(chalk.yellow(message));
|
|
557
|
+
},
|
|
558
|
+
error(message) {
|
|
559
|
+
console.error(chalk.red(message));
|
|
560
|
+
},
|
|
561
|
+
plain(message) {
|
|
562
|
+
console.log(message);
|
|
563
|
+
}
|
|
564
|
+
};
|
|
565
|
+
|
|
566
|
+
// src/core/update-check.ts
|
|
567
|
+
async function checkForUpdate(currentVersion) {
|
|
568
|
+
if (process.env.LAYSOAI_SKIP_UPDATE_CHECK === "1") {
|
|
569
|
+
return null;
|
|
570
|
+
}
|
|
571
|
+
const controller = new AbortController();
|
|
572
|
+
const timeout = setTimeout(() => controller.abort(), 1500);
|
|
573
|
+
try {
|
|
574
|
+
const response = await fetch(`https://registry.npmjs.org/${PACKAGE_NAME}/latest`, {
|
|
575
|
+
signal: controller.signal,
|
|
576
|
+
headers: {
|
|
577
|
+
accept: "application/json",
|
|
578
|
+
"user-agent": `${PACKAGE_NAME}/${currentVersion}`
|
|
579
|
+
}
|
|
580
|
+
});
|
|
581
|
+
if (!response.ok) {
|
|
582
|
+
return null;
|
|
583
|
+
}
|
|
584
|
+
const latest = await response.json();
|
|
585
|
+
if (latest.version && compareVersions(latest.version, currentVersion) > 0) {
|
|
586
|
+
return latest.version;
|
|
587
|
+
}
|
|
588
|
+
return null;
|
|
589
|
+
} catch {
|
|
590
|
+
return null;
|
|
591
|
+
} finally {
|
|
592
|
+
clearTimeout(timeout);
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
function compareVersions(left, right) {
|
|
596
|
+
const leftParts = normalizeVersion(left);
|
|
597
|
+
const rightParts = normalizeVersion(right);
|
|
598
|
+
for (let index = 0; index < Math.max(leftParts.length, rightParts.length); index += 1) {
|
|
599
|
+
const leftPart = leftParts[index] ?? 0;
|
|
600
|
+
const rightPart = rightParts[index] ?? 0;
|
|
601
|
+
if (leftPart > rightPart) {
|
|
602
|
+
return 1;
|
|
603
|
+
}
|
|
604
|
+
if (leftPart < rightPart) {
|
|
605
|
+
return -1;
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
return 0;
|
|
609
|
+
}
|
|
610
|
+
function normalizeVersion(version) {
|
|
611
|
+
return version.replace(/^v/, "").split(/[.-]/).map((part) => Number.parseInt(part, 10)).map((part) => Number.isFinite(part) ? part : 0);
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
// src/cli.ts
|
|
615
|
+
async function runCli(argv = process.argv) {
|
|
616
|
+
const program = new Command();
|
|
617
|
+
program.name(PACKAGE_NAME).description("Configure Claude Code, Codex, and Gemini to use LaysoAI.").version(PACKAGE_VERSION);
|
|
618
|
+
program.command("init", { isDefault: true }).description("Configure selected AI CLI tools with your LaysoAI key.").option("-k, --key <key>", "LaysoAI API key").option("-t, --targets <targets>", "Target CLIs: claude,codex,gemini,all").option("-y, --yes", "Skip confirmation").option("--dry-run", "Show changes without writing files").action(async (options) => {
|
|
619
|
+
await initCommand(options);
|
|
620
|
+
});
|
|
621
|
+
program.command("config").description("Show detected LaysoAI CLI configuration.").action(async () => {
|
|
622
|
+
await configCommand();
|
|
623
|
+
});
|
|
624
|
+
program.command("doctor").description("Check CLI installation and LaysoAI configuration status.").action(async () => {
|
|
625
|
+
await doctorCommand();
|
|
626
|
+
});
|
|
627
|
+
program.command("reset").description("Restore the latest backup generated by laysoai.").option("-t, --targets <targets>", "Target CLIs: claude,codex,gemini,all").option("-y, --yes", "Skip confirmation").action(async (options) => {
|
|
628
|
+
await resetCommand(options);
|
|
629
|
+
});
|
|
630
|
+
try {
|
|
631
|
+
await notifyIfUpdateAvailable();
|
|
632
|
+
await program.parseAsync(argv);
|
|
633
|
+
} catch (error) {
|
|
634
|
+
logger.error(error.message);
|
|
635
|
+
process.exitCode = 1;
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
async function notifyIfUpdateAvailable() {
|
|
639
|
+
const latestVersion = await checkForUpdate(PACKAGE_VERSION);
|
|
640
|
+
if (!latestVersion) {
|
|
641
|
+
return;
|
|
642
|
+
}
|
|
643
|
+
logger.warn(`\u53D1\u73B0\u65B0\u7248 ${PACKAGE_NAME}@${latestVersion}\uFF0C\u5F53\u524D\u7248\u672C ${PACKAGE_VERSION}\u3002\u5EFA\u8BAE\u8FD0\u884C: npm install -g ${PACKAGE_NAME}@latest`);
|
|
644
|
+
}
|
|
645
|
+
async function initCommand(options) {
|
|
646
|
+
const targets = options.targets ? parseTargets(options.targets) : await promptTargets();
|
|
647
|
+
const apiKey = options.key?.trim() || await password({ message: "\u8BF7\u8F93\u5165 LaysoAI API Key:", mask: "*" });
|
|
648
|
+
if (!apiKey.trim()) {
|
|
649
|
+
throw new Error("LaysoAI API Key \u4E0D\u80FD\u4E3A\u7A7A\u3002");
|
|
650
|
+
}
|
|
651
|
+
const input = {
|
|
652
|
+
apiKey: apiKey.trim(),
|
|
653
|
+
targets,
|
|
654
|
+
yes: Boolean(options.yes),
|
|
655
|
+
dryRun: Boolean(options.dryRun)
|
|
656
|
+
};
|
|
657
|
+
logger.info(`LaysoAI \u8BF7\u6C42\u5730\u5740: ${LAYSOAI_BASE_URL}`);
|
|
658
|
+
logger.info(`LaysoAI Key: ${maskSecret(input.apiKey)}`);
|
|
659
|
+
const selectedAdapters = getAdapters(targets);
|
|
660
|
+
await printPreflight(selectedAdapters);
|
|
661
|
+
const plans = [];
|
|
662
|
+
for (const adapter of selectedAdapters) {
|
|
663
|
+
plans.push(await adapter.plan(input));
|
|
664
|
+
}
|
|
665
|
+
printPlans(plans);
|
|
666
|
+
if (input.dryRun) {
|
|
667
|
+
logger.warn("dry-run \u6A21\u5F0F\u672A\u5199\u5165\u4EFB\u4F55\u6587\u4EF6\u3002");
|
|
668
|
+
return;
|
|
669
|
+
}
|
|
670
|
+
if (!input.yes) {
|
|
671
|
+
const approved = await confirm({
|
|
672
|
+
message: "\u786E\u8BA4\u76F4\u63A5\u4FEE\u6539\u4EE5\u4E0A\u672C\u673A CLI \u914D\u7F6E\u6587\u4EF6\u5417\uFF1F",
|
|
673
|
+
default: true
|
|
674
|
+
});
|
|
675
|
+
if (!approved) {
|
|
676
|
+
logger.warn("\u5DF2\u53D6\u6D88\u914D\u7F6E\u3002");
|
|
677
|
+
return;
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
for (const adapter of selectedAdapters) {
|
|
681
|
+
const spinner = ora(`\u6B63\u5728\u914D\u7F6E ${adapter.name}...`).start();
|
|
682
|
+
try {
|
|
683
|
+
const plan = await adapter.apply(input);
|
|
684
|
+
spinner.succeed(`${adapter.name} \u914D\u7F6E\u5B8C\u6210: ${plan.configPath}`);
|
|
685
|
+
if (plan.backupPath) {
|
|
686
|
+
logger.plain(chalk2.dim(` backup: ${plan.backupPath}`));
|
|
687
|
+
}
|
|
688
|
+
} catch (error) {
|
|
689
|
+
spinner.fail(`${adapter.name} \u914D\u7F6E\u5931\u8D25`);
|
|
690
|
+
throw error;
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
logger.success("LaysoAI \u81EA\u52A8\u914D\u7F6E\u5B8C\u6210\u3002");
|
|
694
|
+
logger.plain("");
|
|
695
|
+
logger.plain("\u9A8C\u8BC1\u547D\u4EE4:");
|
|
696
|
+
for (const adapter of selectedAdapters) {
|
|
697
|
+
logger.plain(`- ${adapter.name}: ${adapter.verifyHint().join(" / ")}`);
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
async function printPreflight(selectedAdapters) {
|
|
701
|
+
logger.plain("");
|
|
702
|
+
logger.plain("\u73AF\u5883\u68C0\u67E5:");
|
|
703
|
+
for (const adapter of selectedAdapters) {
|
|
704
|
+
const [detected, current] = await Promise.all([adapter.detect(), adapter.inspect()]);
|
|
705
|
+
logger.plain(`- ${adapter.name}`);
|
|
706
|
+
logger.plain(` CLI: ${detected.installed ? chalk2.green("\u5DF2\u5B89\u88C5") : chalk2.yellow("\u672A\u68C0\u6D4B\u5230")} (${detected.command})`);
|
|
707
|
+
logger.plain(` \u914D\u7F6E\u6587\u4EF6: ${current.exists ? chalk2.green("\u5B58\u5728") : chalk2.yellow("\u4E0D\u5B58\u5728")}`);
|
|
708
|
+
logger.plain(` \u8DEF\u5F84: ${current.configPath}`);
|
|
709
|
+
logger.plain(` LaysoAI: ${current.configured ? chalk2.green("\u5DF2\u914D\u7F6E") : chalk2.yellow("\u672A\u914D\u7F6E")}`);
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
async function configCommand() {
|
|
713
|
+
for (const adapter of Object.values(adapters)) {
|
|
714
|
+
const current = await adapter.inspect();
|
|
715
|
+
const state = current.configured ? chalk2.green("configured") : chalk2.yellow("not configured");
|
|
716
|
+
logger.plain(`${current.name}: ${state}`);
|
|
717
|
+
logger.plain(` path: ${current.configPath}`);
|
|
718
|
+
logger.plain(` ${current.summary}`);
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
async function doctorCommand() {
|
|
722
|
+
logger.info(`LaysoAI \u8BF7\u6C42\u5730\u5740: ${LAYSOAI_BASE_URL}`);
|
|
723
|
+
for (const adapter of Object.values(adapters)) {
|
|
724
|
+
const [detected, current] = await Promise.all([adapter.detect(), adapter.inspect()]);
|
|
725
|
+
logger.plain(`${adapter.name}:`);
|
|
726
|
+
logger.plain(` command: ${detected.installed ? chalk2.green("found") : chalk2.yellow("not found")} (${detected.command})`);
|
|
727
|
+
logger.plain(` config: ${current.configured ? chalk2.green("configured") : chalk2.yellow("not configured")}`);
|
|
728
|
+
logger.plain(` path: ${current.configPath}`);
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
async function resetCommand(options) {
|
|
732
|
+
const targets = options.targets ? parseTargets(options.targets) : await promptTargets();
|
|
733
|
+
const entries = [];
|
|
734
|
+
for (const target of targets) {
|
|
735
|
+
const entry = await latestEntryFor(target);
|
|
736
|
+
if (entry) {
|
|
737
|
+
entries.push(entry);
|
|
738
|
+
} else {
|
|
739
|
+
logger.warn(`${adapters[target].name}: \u6CA1\u6709\u627E\u5230 laysoai \u5907\u4EFD\u8BB0\u5F55\u3002`);
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
if (entries.length === 0) {
|
|
743
|
+
return;
|
|
744
|
+
}
|
|
745
|
+
logger.warn("\u5C06\u6062\u590D\u4EE5\u4E0B\u914D\u7F6E:");
|
|
746
|
+
for (const entry of entries) {
|
|
747
|
+
logger.plain(`- ${adapters[entry.target].name}: ${entry.configPath}`);
|
|
748
|
+
}
|
|
749
|
+
if (!options.yes) {
|
|
750
|
+
const approved = await confirm({
|
|
751
|
+
message: "\u786E\u8BA4\u6062\u590D\u6700\u8FD1\u4E00\u6B21\u5907\u4EFD\u5417\uFF1F",
|
|
752
|
+
default: false
|
|
753
|
+
});
|
|
754
|
+
if (!approved) {
|
|
755
|
+
logger.warn("\u5DF2\u53D6\u6D88\u6062\u590D\u3002");
|
|
756
|
+
return;
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
for (const entry of entries) {
|
|
760
|
+
await restoreEntry(entry);
|
|
761
|
+
logger.success(`${adapters[entry.target].name} \u5DF2\u6062\u590D\u3002`);
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
function printPlans(plans) {
|
|
765
|
+
logger.plain("");
|
|
766
|
+
logger.plain("\u5C06\u6267\u884C\u4EE5\u4E0B\u914D\u7F6E\u4FEE\u6539:");
|
|
767
|
+
for (const plan of plans) {
|
|
768
|
+
logger.plain(`- ${plan.name}`);
|
|
769
|
+
logger.plain(` path: ${plan.configPath}`);
|
|
770
|
+
logger.plain(` before: ${plan.beforeSummary}`);
|
|
771
|
+
logger.plain(` after: ${plan.afterSummary}`);
|
|
772
|
+
for (const change of plan.changes) {
|
|
773
|
+
logger.plain(` ${change.action.padEnd(6)} ${change.key}`);
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
logger.plain("");
|
|
777
|
+
}
|
|
778
|
+
function parseTargets(value) {
|
|
779
|
+
const rawTargets = value.split(",").map((target) => target.trim().toLowerCase()).filter(Boolean);
|
|
780
|
+
if (rawTargets.includes("all")) {
|
|
781
|
+
return [...TARGETS];
|
|
782
|
+
}
|
|
783
|
+
const normalized = rawTargets.map((target) => {
|
|
784
|
+
if (target === "claude-code") {
|
|
785
|
+
return "claude";
|
|
786
|
+
}
|
|
787
|
+
return target;
|
|
788
|
+
});
|
|
789
|
+
const invalid = normalized.filter((target) => !TARGETS.includes(target));
|
|
790
|
+
if (invalid.length > 0) {
|
|
791
|
+
throw new Error(`\u4E0D\u652F\u6301\u7684 targets: ${invalid.join(", ")}\u3002\u53EF\u7528\u503C: claude,codex,gemini,all`);
|
|
792
|
+
}
|
|
793
|
+
const unique = Array.from(new Set(normalized));
|
|
794
|
+
if (unique.length === 0) {
|
|
795
|
+
throw new Error("\u8BF7\u81F3\u5C11\u9009\u62E9\u4E00\u4E2A target\u3002");
|
|
796
|
+
}
|
|
797
|
+
return unique;
|
|
798
|
+
}
|
|
799
|
+
async function promptTargets() {
|
|
800
|
+
const selected = await checkbox({
|
|
801
|
+
message: "\u8BF7\u9009\u62E9\u9700\u8981\u914D\u7F6E\u7684\u5DE5\u5177:",
|
|
802
|
+
choices: [
|
|
803
|
+
{ name: "Claude Code", value: "claude" },
|
|
804
|
+
{ name: "Codex CLI", value: "codex" },
|
|
805
|
+
{ name: "Gemini CLI", value: "gemini" }
|
|
806
|
+
],
|
|
807
|
+
required: true
|
|
808
|
+
});
|
|
809
|
+
return selected;
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
// src/index.ts
|
|
813
|
+
void runCli();
|
|
814
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/cli.ts","../src/constants.ts","../src/adapters/claude.ts","../src/core/backup.ts","../src/core/fs.ts","../src/core/paths.ts","../src/core/commands.ts","../src/core/manifest.ts","../src/core/mask.ts","../src/adapters/codex.ts","../src/core/env-file.ts","../src/adapters/gemini.ts","../src/adapters/index.ts","../src/core/logger.ts","../src/core/update-check.ts","../src/index.ts"],"sourcesContent":["import { checkbox, confirm, password } from '@inquirer/prompts';\nimport chalk from 'chalk';\nimport { Command } from 'commander';\nimport ora from 'ora';\nimport { LAYSOAI_BASE_URL, PACKAGE_NAME, PACKAGE_VERSION, TARGETS, TargetId } from './constants.js';\nimport { adapters, getAdapters } from './adapters/index.js';\nimport { SetupInput } from './adapters/types.js';\nimport { logger } from './core/logger.js';\nimport { maskSecret } from './core/mask.js';\nimport { latestEntryFor, restoreEntry } from './core/manifest.js';\nimport { checkForUpdate } from './core/update-check.js';\n\ntype InitOptions = {\n key?: string;\n targets?: string;\n yes?: boolean;\n dryRun?: boolean;\n};\n\ntype TargetOptions = {\n targets?: string;\n yes?: boolean;\n};\n\nexport async function runCli(argv = process.argv): Promise<void> {\n const program = new Command();\n\n program\n .name(PACKAGE_NAME)\n .description('Configure Claude Code, Codex, and Gemini to use LaysoAI.')\n .version(PACKAGE_VERSION);\n\n program\n .command('init', { isDefault: true })\n .description('Configure selected AI CLI tools with your LaysoAI key.')\n .option('-k, --key <key>', 'LaysoAI API key')\n .option('-t, --targets <targets>', 'Target CLIs: claude,codex,gemini,all')\n .option('-y, --yes', 'Skip confirmation')\n .option('--dry-run', 'Show changes without writing files')\n .action(async (options: InitOptions) => {\n await initCommand(options);\n });\n\n program\n .command('config')\n .description('Show detected LaysoAI CLI configuration.')\n .action(async () => {\n await configCommand();\n });\n\n program\n .command('doctor')\n .description('Check CLI installation and LaysoAI configuration status.')\n .action(async () => {\n await doctorCommand();\n });\n\n program\n .command('reset')\n .description('Restore the latest backup generated by laysoai.')\n .option('-t, --targets <targets>', 'Target CLIs: claude,codex,gemini,all')\n .option('-y, --yes', 'Skip confirmation')\n .action(async (options: TargetOptions) => {\n await resetCommand(options);\n });\n\n try {\n await notifyIfUpdateAvailable();\n await program.parseAsync(argv);\n } catch (error) {\n logger.error((error as Error).message);\n process.exitCode = 1;\n }\n}\n\nasync function notifyIfUpdateAvailable(): Promise<void> {\n const latestVersion = await checkForUpdate(PACKAGE_VERSION);\n if (!latestVersion) {\n return;\n }\n\n logger.warn(`发现新版 ${PACKAGE_NAME}@${latestVersion},当前版本 ${PACKAGE_VERSION}。建议运行: npm install -g ${PACKAGE_NAME}@latest`);\n}\n\nasync function initCommand(options: InitOptions): Promise<void> {\n const targets = options.targets ? parseTargets(options.targets) : await promptTargets();\n const apiKey = options.key?.trim() || (await password({ message: '请输入 LaysoAI API Key:', mask: '*' }));\n\n if (!apiKey.trim()) {\n throw new Error('LaysoAI API Key 不能为空。');\n }\n\n const input: SetupInput = {\n apiKey: apiKey.trim(),\n targets,\n yes: Boolean(options.yes),\n dryRun: Boolean(options.dryRun)\n };\n\n logger.info(`LaysoAI 请求地址: ${LAYSOAI_BASE_URL}`);\n logger.info(`LaysoAI Key: ${maskSecret(input.apiKey)}`);\n\n const selectedAdapters = getAdapters(targets);\n await printPreflight(selectedAdapters);\n\n const plans = [];\n for (const adapter of selectedAdapters) {\n plans.push(await adapter.plan(input));\n }\n\n printPlans(plans);\n\n if (input.dryRun) {\n logger.warn('dry-run 模式未写入任何文件。');\n return;\n }\n\n if (!input.yes) {\n const approved = await confirm({\n message: '确认直接修改以上本机 CLI 配置文件吗?',\n default: true\n });\n if (!approved) {\n logger.warn('已取消配置。');\n return;\n }\n }\n\n for (const adapter of selectedAdapters) {\n const spinner = ora(`正在配置 ${adapter.name}...`).start();\n try {\n const plan = await adapter.apply(input);\n spinner.succeed(`${adapter.name} 配置完成: ${plan.configPath}`);\n if (plan.backupPath) {\n logger.plain(chalk.dim(` backup: ${plan.backupPath}`));\n }\n } catch (error) {\n spinner.fail(`${adapter.name} 配置失败`);\n throw error;\n }\n }\n\n logger.success('LaysoAI 自动配置完成。');\n logger.plain('');\n logger.plain('验证命令:');\n for (const adapter of selectedAdapters) {\n logger.plain(`- ${adapter.name}: ${adapter.verifyHint().join(' / ')}`);\n }\n}\n\nasync function printPreflight(selectedAdapters: ReturnType<typeof getAdapters>): Promise<void> {\n logger.plain('');\n logger.plain('环境检查:');\n for (const adapter of selectedAdapters) {\n const [detected, current] = await Promise.all([adapter.detect(), adapter.inspect()]);\n logger.plain(`- ${adapter.name}`);\n logger.plain(` CLI: ${detected.installed ? chalk.green('已安装') : chalk.yellow('未检测到')} (${detected.command})`);\n logger.plain(` 配置文件: ${current.exists ? chalk.green('存在') : chalk.yellow('不存在')}`);\n logger.plain(` 路径: ${current.configPath}`);\n logger.plain(` LaysoAI: ${current.configured ? chalk.green('已配置') : chalk.yellow('未配置')}`);\n }\n}\n\nasync function configCommand(): Promise<void> {\n for (const adapter of Object.values(adapters)) {\n const current = await adapter.inspect();\n const state = current.configured ? chalk.green('configured') : chalk.yellow('not configured');\n logger.plain(`${current.name}: ${state}`);\n logger.plain(` path: ${current.configPath}`);\n logger.plain(` ${current.summary}`);\n }\n}\n\nasync function doctorCommand(): Promise<void> {\n logger.info(`LaysoAI 请求地址: ${LAYSOAI_BASE_URL}`);\n for (const adapter of Object.values(adapters)) {\n const [detected, current] = await Promise.all([adapter.detect(), adapter.inspect()]);\n logger.plain(`${adapter.name}:`);\n logger.plain(` command: ${detected.installed ? chalk.green('found') : chalk.yellow('not found')} (${detected.command})`);\n logger.plain(` config: ${current.configured ? chalk.green('configured') : chalk.yellow('not configured')}`);\n logger.plain(` path: ${current.configPath}`);\n }\n}\n\nasync function resetCommand(options: TargetOptions): Promise<void> {\n const targets = options.targets ? parseTargets(options.targets) : await promptTargets();\n\n const entries = [];\n for (const target of targets) {\n const entry = await latestEntryFor(target);\n if (entry) {\n entries.push(entry);\n } else {\n logger.warn(`${adapters[target].name}: 没有找到 laysoai 备份记录。`);\n }\n }\n\n if (entries.length === 0) {\n return;\n }\n\n logger.warn('将恢复以下配置:');\n for (const entry of entries) {\n logger.plain(`- ${adapters[entry.target].name}: ${entry.configPath}`);\n }\n\n if (!options.yes) {\n const approved = await confirm({\n message: '确认恢复最近一次备份吗?',\n default: false\n });\n if (!approved) {\n logger.warn('已取消恢复。');\n return;\n }\n }\n\n for (const entry of entries) {\n await restoreEntry(entry);\n logger.success(`${adapters[entry.target].name} 已恢复。`);\n }\n}\n\nfunction printPlans(plans: Awaited<ReturnType<typeof adapters.claude.plan>>[]): void {\n logger.plain('');\n logger.plain('将执行以下配置修改:');\n for (const plan of plans) {\n logger.plain(`- ${plan.name}`);\n logger.plain(` path: ${plan.configPath}`);\n logger.plain(` before: ${plan.beforeSummary}`);\n logger.plain(` after: ${plan.afterSummary}`);\n for (const change of plan.changes) {\n logger.plain(` ${change.action.padEnd(6)} ${change.key}`);\n }\n }\n logger.plain('');\n}\n\nfunction parseTargets(value: string): TargetId[] {\n const rawTargets = value\n .split(',')\n .map((target) => target.trim().toLowerCase())\n .filter(Boolean);\n\n if (rawTargets.includes('all')) {\n return [...TARGETS];\n }\n\n const normalized = rawTargets.map((target) => {\n if (target === 'claude-code') {\n return 'claude';\n }\n return target;\n });\n\n const invalid = normalized.filter((target) => !TARGETS.includes(target as TargetId));\n if (invalid.length > 0) {\n throw new Error(`不支持的 targets: ${invalid.join(', ')}。可用值: claude,codex,gemini,all`);\n }\n\n const unique = Array.from(new Set(normalized)) as TargetId[];\n if (unique.length === 0) {\n throw new Error('请至少选择一个 target。');\n }\n return unique;\n}\n\nasync function promptTargets(): Promise<TargetId[]> {\n const selected = await checkbox<TargetId>({\n message: '请选择需要配置的工具:',\n choices: [\n { name: 'Claude Code', value: 'claude' },\n { name: 'Codex CLI', value: 'codex' },\n { name: 'Gemini CLI', value: 'gemini' }\n ],\n required: true\n });\n\n return selected;\n}\n","export const PACKAGE_NAME = 'laysoai';\nexport const PACKAGE_VERSION = '0.1.0';\nexport const LAYSOAI_BASE_URL = 'https://laysoai.com';\nexport const LAYSOAI_HOME_DIR = '.laysoai';\n\nexport const TARGETS = ['claude', 'codex', 'gemini'] as const;\nexport type TargetId = (typeof TARGETS)[number];\n","import path from 'node:path';\nimport { LAYSOAI_BASE_URL } from '../constants.js';\nimport { backupConfig } from '../core/backup.js';\nimport { commandExists } from '../core/commands.js';\nimport { readJsonObject, writeJsonObject, pathExists } from '../core/fs.js';\nimport { addManifestEntry } from '../core/manifest.js';\nimport { hasValue } from '../core/mask.js';\nimport { homePath } from '../core/paths.js';\nimport { CliAdapter, ConfigChangePlan, CurrentConfig, DetectResult, SetupInput } from './types.js';\n\nconst ENV_VALUES = (apiKey: string) => ({\n ANTHROPIC_BASE_URL: LAYSOAI_BASE_URL,\n ANTHROPIC_AUTH_TOKEN: apiKey,\n ANTHROPIC_API_KEY: apiKey\n});\n\nexport const claudeAdapter: CliAdapter = {\n id: 'claude',\n name: 'Claude Code',\n command: 'claude',\n\n configPath() {\n return homePath('.claude', 'settings.json');\n },\n\n async detect(): Promise<DetectResult> {\n return { command: this.command, installed: await commandExists(this.command) };\n },\n\n async inspect(): Promise<CurrentConfig> {\n const configPath = this.configPath();\n const exists = await pathExists(configPath);\n if (!exists) {\n return {\n target: this.id,\n name: this.name,\n configPath,\n exists,\n configured: false,\n summary: '配置文件不存在'\n };\n }\n\n const config = await readJsonObject(configPath);\n const env = asRecord(config.env);\n const configured = env.ANTHROPIC_BASE_URL === LAYSOAI_BASE_URL && (hasValue(env.ANTHROPIC_AUTH_TOKEN) || hasValue(env.ANTHROPIC_API_KEY));\n\n return {\n target: this.id,\n name: this.name,\n configPath,\n exists,\n configured,\n summary: configured ? '已配置 LaysoAI env' : '未检测到完整 LaysoAI env'\n };\n },\n\n async plan(input: SetupInput): Promise<ConfigChangePlan> {\n const configPath = this.configPath();\n const before = await this.inspect();\n const config = await safeReadJson(configPath);\n const env = asRecord(config.env);\n const desired = ENV_VALUES(input.apiKey);\n\n return {\n target: this.id,\n name: this.name,\n configPath,\n backupPath: null,\n beforeSummary: before.summary,\n afterSummary: '写入 ANTHROPIC_BASE_URL、ANTHROPIC_AUTH_TOKEN、ANTHROPIC_API_KEY',\n changes: Object.entries(desired).map(([key, value]) => ({\n key,\n action: env[key] === value ? 'skip' : key in env ? 'update' : 'add'\n }))\n };\n },\n\n async apply(input: SetupInput): Promise<ConfigChangePlan> {\n const configPath = this.configPath();\n const plan = await this.plan(input);\n const backup = await backupConfig(this.id, configPath);\n\n try {\n const config = await safeReadJson(configPath);\n const env = asRecord(config.env);\n config.env = {\n ...env,\n ...ENV_VALUES(input.apiKey)\n };\n await writeJsonObject(configPath, config);\n } catch (error) {\n if (backup.existed && backup.backupPath) {\n const original = await import('node:fs/promises');\n await original.copyFile(backup.backupPath, configPath);\n }\n throw error;\n }\n\n await addManifestEntry({\n id: backup.id,\n date: new Date().toISOString(),\n target: this.id,\n configPath,\n backupPath: backup.backupPath,\n existed: backup.existed\n });\n\n return { ...plan, backupPath: backup.backupPath };\n },\n\n verifyHint() {\n return ['claude --version', 'claude'];\n }\n};\n\nasync function safeReadJson(configPath: string): Promise<Record<string, unknown>> {\n try {\n return await readJsonObject(configPath);\n } catch (error) {\n throw new Error(`无法读取 Claude Code 配置 ${path.basename(configPath)}:${(error as Error).message}`);\n }\n}\n\nfunction asRecord(value: unknown): Record<string, string> {\n if (!value || typeof value !== 'object' || Array.isArray(value)) {\n return {};\n }\n return value as Record<string, string>;\n}\n","import path from 'node:path';\nimport { TargetId } from '../constants.js';\nimport { copyFileIfExists } from './fs.js';\nimport { laysoaiPath, timestampId } from './paths.js';\n\nexport type BackupResult = {\n id: string;\n backupPath: string | null;\n existed: boolean;\n};\n\nexport async function backupConfig(target: TargetId, configPath: string): Promise<BackupResult> {\n const id = timestampId();\n const backupPath = laysoaiPath('backups', target, id, path.basename(configPath));\n const existed = await copyFileIfExists(configPath, backupPath);\n\n return {\n id,\n backupPath: existed ? backupPath : null,\n existed\n };\n}\n","import fs from 'node:fs/promises';\nimport path from 'node:path';\n\nexport async function pathExists(filePath: string): Promise<boolean> {\n try {\n await fs.access(filePath);\n return true;\n } catch {\n return false;\n }\n}\n\nexport async function ensureDir(dirPath: string): Promise<void> {\n await fs.mkdir(dirPath, { recursive: true });\n}\n\nexport async function readText(filePath: string): Promise<string | null> {\n if (!(await pathExists(filePath))) {\n return null;\n }\n return fs.readFile(filePath, 'utf8');\n}\n\nexport async function writeText(filePath: string, content: string): Promise<void> {\n await ensureDir(path.dirname(filePath));\n await fs.writeFile(filePath, content, 'utf8');\n}\n\nexport async function readJsonObject(filePath: string): Promise<Record<string, unknown>> {\n const content = await readText(filePath);\n if (!content || !content.trim()) {\n return {};\n }\n const parsed = JSON.parse(content) as unknown;\n if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {\n throw new Error(`${filePath} must contain a JSON object.`);\n }\n return parsed as Record<string, unknown>;\n}\n\nexport async function writeJsonObject(filePath: string, value: Record<string, unknown>): Promise<void> {\n await writeText(filePath, `${JSON.stringify(value, null, 2)}\\n`);\n}\n\nexport async function copyFileIfExists(source: string, destination: string): Promise<boolean> {\n if (!(await pathExists(source))) {\n return false;\n }\n await ensureDir(path.dirname(destination));\n await fs.copyFile(source, destination);\n return true;\n}\n\nexport async function removeFileIfExists(filePath: string): Promise<void> {\n if (await pathExists(filePath)) {\n await fs.rm(filePath, { force: true });\n }\n}\n","import os from 'node:os';\nimport path from 'node:path';\nimport { LAYSOAI_HOME_DIR } from '../constants.js';\n\nexport function homePath(...parts: string[]): string {\n return path.join(os.homedir(), ...parts);\n}\n\nexport function laysoaiPath(...parts: string[]): string {\n return homePath(LAYSOAI_HOME_DIR, ...parts);\n}\n\nexport function timestampId(date = new Date()): string {\n return date.toISOString().replace(/[:.]/g, '-');\n}\n","import { spawn } from 'node:child_process';\nimport process from 'node:process';\n\nexport async function commandExists(command: string): Promise<boolean> {\n const checker = process.platform === 'win32' ? 'where' : 'command';\n const args = process.platform === 'win32' ? [command] : ['-v', command];\n\n return new Promise((resolve) => {\n const child = spawn(checker, args, {\n stdio: 'ignore',\n shell: process.platform !== 'win32'\n });\n\n child.on('error', () => resolve(false));\n child.on('close', (code) => resolve(code === 0));\n });\n}\n","import fs from 'node:fs/promises';\nimport path from 'node:path';\nimport { TargetId } from '../constants.js';\nimport { ensureDir, pathExists, readText, writeText } from './fs.js';\nimport { laysoaiPath } from './paths.js';\n\nexport type ManifestEntry = {\n id: string;\n date: string;\n target: TargetId;\n configPath: string;\n backupPath: string | null;\n existed: boolean;\n};\n\ntype Manifest = {\n version: 1;\n entries: ManifestEntry[];\n};\n\nconst MANIFEST_PATH = laysoaiPath('manifest.json');\n\nasync function readManifest(): Promise<Manifest> {\n const content = await readText(MANIFEST_PATH);\n if (!content) {\n return { version: 1, entries: [] };\n }\n const parsed = JSON.parse(content) as Manifest;\n return {\n version: 1,\n entries: Array.isArray(parsed.entries) ? parsed.entries : []\n };\n}\n\nasync function writeManifest(manifest: Manifest): Promise<void> {\n await ensureDir(path.dirname(MANIFEST_PATH));\n await writeText(MANIFEST_PATH, `${JSON.stringify(manifest, null, 2)}\\n`);\n}\n\nexport async function addManifestEntry(entry: ManifestEntry): Promise<void> {\n const manifest = await readManifest();\n manifest.entries.push(entry);\n await writeManifest(manifest);\n}\n\nexport async function latestEntryFor(target: TargetId): Promise<ManifestEntry | null> {\n const manifest = await readManifest();\n const matches = manifest.entries.filter((entry) => entry.target === target);\n return matches.at(-1) ?? null;\n}\n\nexport async function restoreEntry(entry: ManifestEntry): Promise<void> {\n if (entry.existed && entry.backupPath) {\n if (!(await pathExists(entry.backupPath))) {\n throw new Error(`Backup file not found: ${entry.backupPath}`);\n }\n await ensureDir(path.dirname(entry.configPath));\n await fs.copyFile(entry.backupPath, entry.configPath);\n return;\n }\n\n await fs.rm(entry.configPath, { force: true });\n}\n","export function maskSecret(secret: string): string {\n if (secret.length <= 8) {\n return '*'.repeat(secret.length);\n }\n return `${secret.slice(0, 4)}...${secret.slice(-4)}`;\n}\n\nexport function hasValue(value: unknown): boolean {\n return typeof value === 'string' && value.trim().length > 0;\n}\n","import * as TOML from 'smol-toml';\nimport { LAYSOAI_BASE_URL } from '../constants.js';\nimport { backupConfig } from '../core/backup.js';\nimport { commandExists } from '../core/commands.js';\nimport { pathExists, readText, writeText } from '../core/fs.js';\nimport { addManifestEntry } from '../core/manifest.js';\nimport { homePath } from '../core/paths.js';\nimport { CliAdapter, ConfigChangePlan, CurrentConfig, DetectResult, SetupInput } from './types.js';\n\ntype TomlObject = Record<string, unknown>;\n\nexport const codexAdapter: CliAdapter = {\n id: 'codex',\n name: 'Codex CLI',\n command: 'codex',\n\n configPath() {\n return homePath('.codex', 'config.toml');\n },\n\n async detect(): Promise<DetectResult> {\n return { command: this.command, installed: await commandExists(this.command) };\n },\n\n async inspect(): Promise<CurrentConfig> {\n const configPath = this.configPath();\n const exists = await pathExists(configPath);\n if (!exists) {\n return {\n target: this.id,\n name: this.name,\n configPath,\n exists,\n configured: false,\n summary: '配置文件不存在'\n };\n }\n\n const config = await readToml(configPath);\n const providers = asRecord(config.model_providers);\n const laysoai = asRecord(providers.laysoai);\n const configured = config.model_provider === 'laysoai' && laysoai.base_url === LAYSOAI_BASE_URL;\n\n return {\n target: this.id,\n name: this.name,\n configPath,\n exists,\n configured,\n summary: configured ? '已配置 LaysoAI provider' : '未检测到 LaysoAI provider'\n };\n },\n\n async plan(input: SetupInput): Promise<ConfigChangePlan> {\n const configPath = this.configPath();\n const before = await this.inspect();\n const config = await readToml(configPath);\n const providers = asRecord(config.model_providers);\n const laysoai = asRecord(providers.laysoai);\n\n const desired = {\n model_provider: 'laysoai',\n 'model_providers.laysoai.name': 'LaysoAI',\n 'model_providers.laysoai.base_url': LAYSOAI_BASE_URL,\n 'model_providers.laysoai.wire_api': 'responses',\n 'model_providers.laysoai.experimental_bearer_token': input.apiKey\n };\n\n return {\n target: this.id,\n name: this.name,\n configPath,\n backupPath: null,\n beforeSummary: before.summary,\n afterSummary: '写入 Codex LaysoAI provider 并设为默认 provider',\n changes: [\n { key: 'model_provider', action: config.model_provider === desired.model_provider ? 'skip' : 'update' },\n { key: 'model_providers.laysoai.name', action: laysoai.name === desired['model_providers.laysoai.name'] ? 'skip' : keyAction(laysoai, 'name') },\n { key: 'model_providers.laysoai.base_url', action: laysoai.base_url === desired['model_providers.laysoai.base_url'] ? 'skip' : keyAction(laysoai, 'base_url') },\n { key: 'model_providers.laysoai.wire_api', action: laysoai.wire_api === desired['model_providers.laysoai.wire_api'] ? 'skip' : keyAction(laysoai, 'wire_api') },\n {\n key: 'model_providers.laysoai.experimental_bearer_token',\n action: laysoai.experimental_bearer_token === input.apiKey ? 'skip' : keyAction(laysoai, 'experimental_bearer_token')\n }\n ]\n };\n },\n\n async apply(input: SetupInput): Promise<ConfigChangePlan> {\n const configPath = this.configPath();\n const plan = await this.plan(input);\n const backup = await backupConfig(this.id, configPath);\n\n try {\n const config = await readToml(configPath);\n const providers = asRecord(config.model_providers);\n config.model_provider = 'laysoai';\n config.model_providers = {\n ...providers,\n laysoai: {\n ...asRecord(providers.laysoai),\n name: 'LaysoAI',\n base_url: LAYSOAI_BASE_URL,\n wire_api: 'responses',\n experimental_bearer_token: input.apiKey\n }\n };\n await writeText(configPath, TOML.stringify(config));\n } catch (error) {\n if (backup.existed && backup.backupPath) {\n const fs = await import('node:fs/promises');\n await fs.copyFile(backup.backupPath, configPath);\n }\n throw error;\n }\n\n await addManifestEntry({\n id: backup.id,\n date: new Date().toISOString(),\n target: this.id,\n configPath,\n backupPath: backup.backupPath,\n existed: backup.existed\n });\n\n return { ...plan, backupPath: backup.backupPath };\n },\n\n verifyHint() {\n return ['codex --version', 'codex'];\n }\n};\n\nasync function readToml(configPath: string): Promise<TomlObject> {\n const content = await readText(configPath);\n if (!content?.trim()) {\n return {};\n }\n const parsed = TOML.parse(content);\n if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {\n throw new Error(`${configPath} must contain a TOML object.`);\n }\n return parsed as TomlObject;\n}\n\nfunction asRecord(value: unknown): TomlObject {\n if (!value || typeof value !== 'object' || Array.isArray(value)) {\n return {};\n }\n return value as TomlObject;\n}\n\nfunction keyAction(record: TomlObject, key: string): 'add' | 'update' {\n return key in record ? 'update' : 'add';\n}\n","export function upsertEnvValues(content: string, values: Record<string, string>): string {\n const lines = content ? content.split(/\\r?\\n/) : [];\n const seen = new Set<string>();\n const nextLines = lines.map((line) => {\n const match = line.match(/^\\s*([A-Za-z_][A-Za-z0-9_]*)\\s*=/);\n if (!match) {\n return line;\n }\n\n const key = match[1];\n if (!(key in values)) {\n return line;\n }\n\n seen.add(key);\n return `${key}=${quoteEnv(values[key])}`;\n });\n\n for (const [key, value] of Object.entries(values)) {\n if (!seen.has(key)) {\n nextLines.push(`${key}=${quoteEnv(value)}`);\n }\n }\n\n return `${nextLines.filter((line, index) => line.length > 0 || index < nextLines.length - 1).join('\\n')}\\n`;\n}\n\nfunction quoteEnv(value: string): string {\n return JSON.stringify(value);\n}\n","import { LAYSOAI_BASE_URL } from '../constants.js';\nimport { backupConfig } from '../core/backup.js';\nimport { commandExists } from '../core/commands.js';\nimport { upsertEnvValues } from '../core/env-file.js';\nimport { pathExists, readText, writeText } from '../core/fs.js';\nimport { addManifestEntry } from '../core/manifest.js';\nimport { homePath } from '../core/paths.js';\nimport { CliAdapter, ConfigChangePlan, CurrentConfig, DetectResult, SetupInput } from './types.js';\n\nconst ENV_VALUES = (apiKey: string) => ({\n GEMINI_API_KEY: apiKey,\n GOOGLE_GEMINI_BASE_URL: LAYSOAI_BASE_URL\n});\n\nexport const geminiAdapter: CliAdapter = {\n id: 'gemini',\n name: 'Gemini CLI',\n command: 'gemini',\n\n configPath() {\n return homePath('.gemini', '.env');\n },\n\n async detect(): Promise<DetectResult> {\n return { command: this.command, installed: await commandExists(this.command) };\n },\n\n async inspect(): Promise<CurrentConfig> {\n const configPath = this.configPath();\n const exists = await pathExists(configPath);\n if (!exists) {\n return {\n target: this.id,\n name: this.name,\n configPath,\n exists,\n configured: false,\n summary: '配置文件不存在'\n };\n }\n\n const content = (await readText(configPath)) ?? '';\n const configured = hasEnvLine(content, 'GEMINI_API_KEY') && hasEnvLine(content, 'GOOGLE_GEMINI_BASE_URL', LAYSOAI_BASE_URL);\n\n return {\n target: this.id,\n name: this.name,\n configPath,\n exists,\n configured,\n summary: configured ? '已配置 LaysoAI env' : '未检测到完整 LaysoAI env'\n };\n },\n\n async plan(input: SetupInput): Promise<ConfigChangePlan> {\n const configPath = this.configPath();\n const before = await this.inspect();\n const content = (await readText(configPath)) ?? '';\n const desired = ENV_VALUES(input.apiKey);\n\n return {\n target: this.id,\n name: this.name,\n configPath,\n backupPath: null,\n beforeSummary: before.summary,\n afterSummary: '写入 GEMINI_API_KEY、GOOGLE_GEMINI_BASE_URL',\n changes: Object.entries(desired).map(([key, value]) => ({\n key,\n action: hasEnvLine(content, key, value) ? 'skip' : hasEnvLine(content, key) ? 'update' : 'add'\n }))\n };\n },\n\n async apply(input: SetupInput): Promise<ConfigChangePlan> {\n const configPath = this.configPath();\n const plan = await this.plan(input);\n const backup = await backupConfig(this.id, configPath);\n\n try {\n const content = (await readText(configPath)) ?? '';\n await writeText(configPath, upsertEnvValues(content, ENV_VALUES(input.apiKey)));\n } catch (error) {\n if (backup.existed && backup.backupPath) {\n const fs = await import('node:fs/promises');\n await fs.copyFile(backup.backupPath, configPath);\n }\n throw error;\n }\n\n await addManifestEntry({\n id: backup.id,\n date: new Date().toISOString(),\n target: this.id,\n configPath,\n backupPath: backup.backupPath,\n existed: backup.existed\n });\n\n return { ...plan, backupPath: backup.backupPath };\n },\n\n verifyHint() {\n return ['gemini --version', 'gemini'];\n }\n};\n\nfunction hasEnvLine(content: string, key: string, value?: string): boolean {\n const matcher = new RegExp(`^\\\\s*${escapeRegExp(key)}\\\\s*=\\\\s*(.*)\\\\s*$`, 'm');\n const match = content.match(matcher);\n if (!match) {\n return false;\n }\n if (value === undefined) {\n return true;\n }\n return unquote(match[1].trim()) === value;\n}\n\nfunction unquote(value: string): string {\n if ((value.startsWith('\"') && value.endsWith('\"')) || (value.startsWith(\"'\") && value.endsWith(\"'\"))) {\n return value.slice(1, -1);\n }\n return value;\n}\n\nfunction escapeRegExp(value: string): string {\n return value.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\n","import { TargetId } from '../constants.js';\nimport { claudeAdapter } from './claude.js';\nimport { codexAdapter } from './codex.js';\nimport { geminiAdapter } from './gemini.js';\nimport { CliAdapter } from './types.js';\n\nexport const adapters: Record<TargetId, CliAdapter> = {\n claude: claudeAdapter,\n codex: codexAdapter,\n gemini: geminiAdapter\n};\n\nexport function getAdapters(targets: TargetId[]): CliAdapter[] {\n return targets.map((target) => adapters[target]);\n}\n","import chalk from 'chalk';\n\nexport const logger = {\n info(message: string): void {\n console.log(chalk.cyan(message));\n },\n success(message: string): void {\n console.log(chalk.green(message));\n },\n warn(message: string): void {\n console.log(chalk.yellow(message));\n },\n error(message: string): void {\n console.error(chalk.red(message));\n },\n plain(message: string): void {\n console.log(message);\n }\n};\n","import { PACKAGE_NAME } from '../constants.js';\n\ntype RegistryLatest = {\n version?: string;\n};\n\nexport async function checkForUpdate(currentVersion: string): Promise<string | null> {\n if (process.env.LAYSOAI_SKIP_UPDATE_CHECK === '1') {\n return null;\n }\n\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), 1500);\n\n try {\n const response = await fetch(`https://registry.npmjs.org/${PACKAGE_NAME}/latest`, {\n signal: controller.signal,\n headers: {\n accept: 'application/json',\n 'user-agent': `${PACKAGE_NAME}/${currentVersion}`\n }\n });\n\n if (!response.ok) {\n return null;\n }\n\n const latest = (await response.json()) as RegistryLatest;\n if (latest.version && compareVersions(latest.version, currentVersion) > 0) {\n return latest.version;\n }\n return null;\n } catch {\n return null;\n } finally {\n clearTimeout(timeout);\n }\n}\n\nexport function compareVersions(left: string, right: string): number {\n const leftParts = normalizeVersion(left);\n const rightParts = normalizeVersion(right);\n\n for (let index = 0; index < Math.max(leftParts.length, rightParts.length); index += 1) {\n const leftPart = leftParts[index] ?? 0;\n const rightPart = rightParts[index] ?? 0;\n if (leftPart > rightPart) {\n return 1;\n }\n if (leftPart < rightPart) {\n return -1;\n }\n }\n\n return 0;\n}\n\nfunction normalizeVersion(version: string): number[] {\n return version\n .replace(/^v/, '')\n .split(/[.-]/)\n .map((part) => Number.parseInt(part, 10))\n .map((part) => (Number.isFinite(part) ? part : 0));\n}\n","#!/usr/bin/env node\nimport { runCli } from './cli.js';\n\nvoid runCli();\n"],"mappings":"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","names":["chalk","path","path","path","path","path","process","fs","path","path","fs","path","asRecord","fs","ENV_VALUES","fs","chalk"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "laysoai",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "LaysoAI CLI scaffold for configuring Claude Code, Codex, and Gemini.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"laysoai": "dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist",
|
|
11
|
+
"README.md",
|
|
12
|
+
"LICENSE"
|
|
13
|
+
],
|
|
14
|
+
"scripts": {
|
|
15
|
+
"build": "tsup src/index.ts --format esm --dts --clean --sourcemap",
|
|
16
|
+
"dev": "tsx src/index.ts",
|
|
17
|
+
"typecheck": "tsc --noEmit",
|
|
18
|
+
"test": "vitest run",
|
|
19
|
+
"prepublishOnly": "npm run typecheck && npm run test && npm run build"
|
|
20
|
+
},
|
|
21
|
+
"keywords": [
|
|
22
|
+
"laysoai",
|
|
23
|
+
"claude-code",
|
|
24
|
+
"codex",
|
|
25
|
+
"gemini",
|
|
26
|
+
"cli"
|
|
27
|
+
],
|
|
28
|
+
"author": "",
|
|
29
|
+
"license": "MIT",
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"@inquirer/prompts": "^7.3.3",
|
|
32
|
+
"chalk": "^5.4.1",
|
|
33
|
+
"commander": "^13.1.0",
|
|
34
|
+
"ora": "^8.2.0",
|
|
35
|
+
"smol-toml": "^1.3.1",
|
|
36
|
+
"zod": "^3.24.2"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@types/node": "^22.13.5",
|
|
40
|
+
"tsup": "^8.3.6",
|
|
41
|
+
"tsx": "^4.19.3",
|
|
42
|
+
"typescript": "^5.7.3",
|
|
43
|
+
"vitest": "^3.0.6"
|
|
44
|
+
},
|
|
45
|
+
"engines": {
|
|
46
|
+
"node": ">=18"
|
|
47
|
+
}
|
|
48
|
+
}
|