dsh-codex-oauth 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,47 +2,68 @@
2
2
 
3
3
  [English](README.md) | [中文](README.zh.md)
4
4
 
5
- DSH adapter that uses the same OpenAI Codex OAuth credential as Pi:
6
- `~/.pi/agent/auth.json` → `openai-codex`.
5
+ A standalone [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) plugin for using ChatGPT Plus/Pro Codex models through OpenAI's Codex OAuth flow. Pi is **not** required.
7
6
 
8
- It uses Pi's installed `@earendil-works/pi-ai` Codex provider and DSH's public
9
- `dsh-llm-pi-ai` adapter, including the official Codex Responses transport, images,
10
- tool calls, reasoning replay, compaction, and OAuth refresh. It does not copy the
11
- token into an API-key environment variable.
7
+ It uses the official `openaiCodexProvider` from `@earendil-works/pi-ai` and DSH's public `dsh-llm-pi-ai` adapter. It supports streaming, tool calls, reasoning, images, replay, compaction, automatic token refresh, and the Codex model catalog.
8
+
9
+ This is the ChatGPT Codex backend, not a general OpenAI Platform API-key adapter.
12
10
 
13
11
  ## Install
14
12
 
15
13
  ```bash
16
14
  dsh plugin --profile web add dsh-codex-oauth
15
+ dsh web
17
16
  ```
18
17
 
19
- For a manual profile install, add the package to the profile and include this
20
- entry in its `cordis.patch.yml`:
18
+ For an `npx` installation:
21
19
 
22
- ```yaml
23
- - insert:
24
- - id: llm-codex-oauth
25
- name: dsh-codex-oauth
20
+ ```bash
21
+ npx @deepseek-ai/dsh plugin --profile web add dsh-codex-oauth
22
+ npx @deepseek-ai/dsh web
26
23
  ```
27
24
 
28
- The profile must also include the normal `llm` service. Restart:
25
+ ## Sign in
26
+
27
+ The plugin owns its own credential file and login flow. No Pi installation or token copying is needed.
28
+
29
+ ### CLI
29
30
 
30
31
  ```bash
31
- npx @deepseek-ai/dsh web
32
+ dsh plugin --profile web exec dsh-openai-codex login
33
+ # headless machines:
34
+ dsh plugin --profile web exec dsh-openai-codex login --device-code
35
+
36
+ dsh plugin --profile web exec dsh-openai-codex status
37
+ dsh plugin --profile web exec dsh-openai-codex logout
32
38
  ```
33
39
 
34
- Then select provider `openai-codex` in the model picker.
40
+ The browser login opens OpenAI's authorization page and completes through a localhost callback. `--device-code` is the fallback for machines without a usable browser.
41
+
42
+ After signing in, choose `openai-codex` in the DSH model picker.
43
+
44
+ ### Credentials
45
+
46
+ Credentials are stored at:
47
+
48
+ ```text
49
+ $DSH_HOME/.openai-codex-auth.json
50
+ ```
51
+
52
+ The default is `~/.dsh/.openai-codex-auth.json`. The file is written atomically with owner-only permissions, and refresh/login/logout operations use a cross-process file lock. The plugin never reads or modifies `~/.pi/agent/auth.json` or `~/.codex/auth.json`.
53
+
54
+ ## Development
55
+
56
+ ```bash
57
+ npm test
58
+ ```
35
59
 
36
- ## Scope
60
+ The test requires the DSH and `pi-ai` peer dependencies supplied by a DSH profile.
37
61
 
38
- The plugin delegates message conversion, streaming, image attachments, tool calls,
39
- reasoning replay, and compaction to DSH's public `dsh-llm-pi-ai` adapter. It only
40
- bridges the credential store to Pi's existing OAuth document.
62
+ ## Limitations
41
63
 
42
- The plugin reads/writes `~/.pi/agent/auth.json` and never prints token values.
43
- Because Pi and DSH do not share a cross-application lock, avoid starting both at the
44
- same time while an OAuth refresh may occur; a future version may use a separate DSH
45
- credential file and its own login flow to remove that race.
64
+ - ChatGPT plan eligibility, model access, quotas, and backend behavior are controlled by OpenAI and may change.
65
+ - Codex endpoints are not public OpenAI Platform API endpoints.
66
+ - Filesystem, shell, MCP, permissions, attachments, and other DSH capabilities come from the active profile.
46
67
 
47
68
  ## License
48
69
 
package/README.zh.md CHANGED
@@ -2,71 +2,67 @@
2
2
 
3
3
  [English](README.md) | 中文
4
4
 
5
- 让 [DeepSeek Harness(dsh)](https://github.com/deepseek-ai/deepseek-harness)
6
- 复用 Pi 已登录的 ChatGPT Plus/Pro Codex OAuth,在不使用 OpenAI Platform API Key 的情况下调用 `openai-codex` 模型。
5
+ 一个独立的 [DeepSeek Harness(dsh)](https://github.com/deepseek-ai/deepseek-harness) 插件,通过 OpenAI Codex OAuth 使用 ChatGPT Plus/Pro 订阅模型。
7
6
 
8
- 本插件不是普通的 OpenAI API 适配器,也不会把 ChatGPT 订阅变成通用 API 凭据。它使用 Pi 的 Codex provider、官方 Codex Responses 传输和 DSH 公共的 `dsh-llm-pi-ai` 适配器。
7
+ **不需要安装 Pi。** 插件使用 `@earendil-works/pi-ai` 提供的 Codex provider,以及 DSH 公共的 `dsh-llm-pi-ai` 适配器,支持流式输出、工具调用、推理、图片、replay、上下文压缩、OAuth 自动刷新和 Codex 模型目录。
9
8
 
10
- ## 功能
11
-
12
- - 复用 `~/.pi/agent/auth.json` 中的 `openai-codex` OAuth 凭据;
13
- - access token 过期时自动使用 refresh token 更新;
14
- - 使用 Pi 同款 Codex 模型目录;
15
- - 支持流式输出、工具调用、推理、图片输入、replay 和上下文压缩;
16
- - 不读取或打印 API Key,也不会把 token 放进环境变量。
9
+ 这是 ChatGPT Codex 后端接入,不是普通的 OpenAI Platform API Key 适配器。
17
10
 
18
11
  ## 安装
19
12
 
20
- 将插件安装到 dsh 的 `web` profile:
21
-
22
13
  ```bash
23
14
  dsh plugin --profile web add dsh-codex-oauth
24
15
  dsh web
25
16
  ```
26
17
 
27
- 如果使用的是 `npx` 版本:
18
+ 如果使用 `npx`:
28
19
 
29
20
  ```bash
30
21
  npx @deepseek-ai/dsh plugin --profile web add dsh-codex-oauth
31
22
  npx @deepseek-ai/dsh web
32
23
  ```
33
24
 
34
- 启动后,在模型选择器中选择:
25
+ ## 登录
35
26
 
36
- ```text
37
- Provider: openai-codex
38
- ```
27
+ 插件拥有独立的 OAuth 登录流程和凭据文件。新用户不需要安装 Pi,也不需要复制任何 token。
39
28
 
40
- 也可以手动把下面的条目加入 profile 的 `cordis.patch.yml`:
29
+ ### 命令行登录
41
30
 
42
- ```yaml
43
- - insert:
44
- - id: llm-codex-oauth
45
- name: dsh-codex-oauth
31
+ ```bash
32
+ dsh plugin --profile web exec dsh-openai-codex login
46
33
  ```
47
34
 
48
- ## 前置条件
35
+ 无图形浏览器的机器:
49
36
 
50
- Pi 必须已经完成 OpenAI Codex 登录,并且存在:
37
+ ```bash
38
+ dsh plugin --profile web exec dsh-openai-codex login --device-code
39
+ ```
51
40
 
52
- ```text
53
- ~/.pi/agent/auth.json
41
+ 查看状态和退出登录:
42
+
43
+ ```bash
44
+ dsh plugin --profile web exec dsh-openai-codex status
45
+ dsh plugin --profile web exec dsh-openai-codex logout
54
46
  ```
55
47
 
56
- 文件中应有 `openai-codex` OAuth 条目。插件不会替用户登录,也不会要求复制 token。
48
+ 浏览器登录会打开 OpenAI 授权页面,并通过 localhost 回调完成。登录后,在 DSH 模型选择器中选择 `openai-codex`。
57
49
 
58
- ## 凭据与并发注意事项
50
+ ### 凭据存储
59
51
 
60
- 插件只使用 Pi 的现有凭据文件,并会在 OAuth 刷新后写回该文件。Pi 与 DSH 并不共享同一个跨应用锁,因此在 token 可能刷新时,不建议同时运行 Pi 和 DSH。
52
+ 凭据默认存储在:
61
53
 
62
- 卸载插件不会删除 Pi 的登录凭据。若要退出登录,请通过 Pi 自己的 `/logout` 完成。
54
+ ```text
55
+ ~/.dsh/.openai-codex-auth.json
56
+ ```
63
57
 
64
- ## 限制
58
+ 也可以通过 `DSH_HOME` 改变目录。文件采用原子写入、仅所有者可读写(`0600`),登录、刷新和退出登录使用跨进程文件锁。
65
59
 
66
- - ChatGPT 订阅权限、模型目录、配额和 Codex 后端行为由 OpenAI 控制,可能变化;
67
- - 插件依赖 DSH 的公共 `dsh-llm-pi-ai` 适配器,需与当前 DSH 版本兼容;
68
- - 这是 Codex 后端接入,不是公开的 OpenAI Platform API;
69
- - DSH 的其他能力(文件系统、Shell、MCP、权限、附件等)仍由当前 profile 提供。
60
+ 插件**不会**读取或修改:
61
+
62
+ ```text
63
+ ~/.pi/agent/auth.json
64
+ ~/.codex/auth.json
65
+ ```
70
66
 
71
67
  ## 开发与测试
72
68
 
@@ -74,7 +70,13 @@ Pi 必须已经完成 OpenAI Codex 登录,并且存在:
74
70
  npm test
75
71
  ```
76
72
 
77
- 测试需要 DSH 和 Pi 的依赖已经安装或由 DSH profile 提供。
73
+ 测试需要当前 DSH profile 提供 DSH 和 `pi-ai` peer dependencies。
74
+
75
+ ## 限制
76
+
77
+ - ChatGPT 套餐资格、模型权限、配额和后端行为由 OpenAI 控制,可能变化;
78
+ - Codex 端点不是公开的 OpenAI Platform API 端点;
79
+ - 文件系统、Shell、MCP、权限、附件等能力仍由当前 DSH profile 提供。
78
80
 
79
81
  ## 许可证
80
82
 
package/SPEC.md ADDED
@@ -0,0 +1,73 @@
1
+ # dsh-codex-oauth 独立登录规格
2
+
3
+ ## 目标
4
+
5
+ 让新用户只安装 DSH 插件即可登录并使用 ChatGPT Codex 订阅,不要求安装 Pi,也不读取或修改 `~/.pi/agent/auth.json`。
6
+
7
+ ## 用户流程
8
+
9
+ ### CLI(第一版)
10
+
11
+ 1. 安装插件并重启 DSH;
12
+ 2. 执行 `dsh-openai-codex login`;
13
+ 3. 插件打开 OpenAI OAuth 授权页;
14
+ 4. 用户在浏览器中批准;
15
+ 5. localhost 回调完成登录;
16
+ 6. DSH 使用 `openai-codex` 模型。
17
+
18
+ 无图形浏览器时使用 `login --device-code`。
19
+
20
+ ### Web(后续)
21
+
22
+ 后续可在 DSH 设置页增加登录按钮;第一版不承诺 Web 登录入口。
23
+
24
+ ### 命令
25
+
26
+ 提供:
27
+
28
+ ```bash
29
+ dsh plugin --profile web exec dsh-openai-codex login
30
+ dsh plugin --profile web exec dsh-openai-codex status
31
+ dsh plugin --profile web exec dsh-openai-codex logout
32
+ ```
33
+
34
+ 无图形浏览器时提供 `login --device-code`。
35
+
36
+ ## 凭据
37
+
38
+ - 存储位置:`$DSH_HOME/.openai-codex-auth.json`,默认 `~/.dsh/.openai-codex-auth.json`;
39
+ - 格式:版本化 JSON,字段为 `version` 和 `credential`;
40
+ - `credential` 保留 `type`, `access`, `refresh`, `expires`, `accountId`;
41
+ - 原子写入,文件权限 `0600`,目录权限 `0700`;
42
+ - 刷新、登录、登出使用跨进程文件锁;
43
+ - 不读取、复制、修改 `~/.pi/agent/auth.json` 或 `~/.codex/auth.json`。
44
+
45
+ ## API 与模型
46
+
47
+ - 使用 `@earendil-works/pi-ai` 的 `openaiCodexProvider`;
48
+ - 使用 DSH 公共 `PiAiAdapter`;
49
+ - 使用 Codex Responses endpoint,不伪装成普通 OpenAI Platform API;
50
+ - 保留流式输出、工具调用、推理、图片、replay 和 compaction;
51
+ - 新建 agent 默认模型沿用 Codex catalog 中的可用模型,不硬编码不存在的模型。
52
+
53
+ ## 安全边界
54
+
55
+ - OAuth 密码、授权码、access token、refresh token 不进入日志、session、Git 或错误信息;
56
+ - Web 登录回调只接受 localhost 同源请求,并返回 `no-store`;
57
+ - 插件移除不自动删除凭据;登出必须由用户明确执行;
58
+ - 不实现普通 OpenAI API Key 兼容层。
59
+
60
+ ## 明确不做
61
+
62
+ - 不依赖 Pi;
63
+ - 不导入 Pi 的 auth 文件;
64
+ - 不修改 DSH 源码;
65
+ - 第一版不加入独立搜索、图片生成和额度面板;这些属于后续功能。
66
+
67
+ ## 验收标准
68
+
69
+ - 干净环境安装插件后,`status` 返回 signed out;
70
+ - CLI OAuth 登录后,`status` 返回 signed in,且不打印 token;
71
+ - access token 过期后请求会自动刷新并持久化;
72
+ - 删除 Pi auth 文件不影响 DSH 插件;
73
+ - Web profile 能加载 `openai-codex`,且公共 DSH adapter 的图片、工具和 replay 测试继续通过。
@@ -0,0 +1,52 @@
1
+ #!/usr/bin/env node
2
+ import { spawn } from "node:child_process";
3
+ import { createInterface } from "node:readline/promises";
4
+ import { authPath, login, logout, status } from "../lib/auth.js";
5
+
6
+ function openBrowser(raw) {
7
+ const url = new URL(raw);
8
+ if (url.protocol !== "https:") throw new Error("Refusing to open a non-HTTPS OAuth URL");
9
+ const command = process.platform === "darwin" ? ["open", url.href] : process.platform === "win32" ? ["rundll32.exe", "url.dll,FileProtocolHandler", url.href] : ["xdg-open", url.href];
10
+ const child = spawn(command[0], command.slice(1), { detached: true, stdio: "ignore", windowsHide: true });
11
+ child.on("error", () => {}); child.unref();
12
+ }
13
+ function safeMessage(error) {
14
+ return String(error?.message ?? error)
15
+ .replace(/\beyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\b/gu, "[redacted token]")
16
+ .replace(/(\b(?:code|token|refresh_token|access_token)=)[^&\s]+/giu, "$1[redacted]");
17
+ }
18
+ function help() {
19
+ console.log("Usage: dsh-openai-codex <login|logout|status> [--device-code]");
20
+ }
21
+
22
+ const [action, ...flags] = process.argv.slice(2);
23
+ if (!action || action === "--help" || action === "-h") { help(); process.exit(0); }
24
+ if (!["login", "logout", "status"].includes(action) || flags.some((flag) => flag !== "--device-code") || (action !== "login" && flags.length)) { help(); process.exit(1); }
25
+ try {
26
+ if (action === "status") {
27
+ const value = await status();
28
+ if (!value.authenticated) { console.log("OpenAI Codex for dsh: signed out"); process.exitCode = 1; }
29
+ else console.log(`OpenAI Codex for dsh: signed in; refresh is automatic`);
30
+ } else if (action === "logout") {
31
+ await logout(); console.log(`OpenAI Codex for dsh: signed out; removed ${authPath()}`);
32
+ } else {
33
+ const readline = createInterface({ input: process.stdin, output: process.stdout });
34
+ try {
35
+ await login(undefined, {
36
+ prompt: async (prompt) => {
37
+ if (prompt.type === "select") return flags.includes("--device-code") ? "device_code" : "browser";
38
+ return readline.question(`${prompt.message}: `, { signal: prompt.signal });
39
+ },
40
+ notify: (event) => {
41
+ if (event.type === "auth_url") { console.log(`Open this URL to sign in:\n${event.url}`); openBrowser(event.url); }
42
+ else if (event.type === "device_code") { console.log(`Open ${event.verificationUri} and enter ${event.userCode}`); openBrowser(event.verificationUri); }
43
+ else if (event.message) console.log(event.message);
44
+ },
45
+ });
46
+ } finally { readline.close(); }
47
+ console.log(`OpenAI Codex for dsh: signed in; credentials saved to ${authPath()}`);
48
+ }
49
+ } catch (error) {
50
+ console.error(`dsh-openai-codex: ${safeMessage(error)}`);
51
+ process.exitCode = 1;
52
+ }
package/lib/auth.js ADDED
@@ -0,0 +1,21 @@
1
+ import { createModels } from "@earendil-works/pi-ai";
2
+ import { openaiCodexProvider } from "@earendil-works/pi-ai/providers/openai-codex";
3
+ import { CodexCredentialStore, OPENAI_CODEX_PROVIDER, authPath } from "./store.js";
4
+
5
+ function models(store) {
6
+ const value = createModels({ credentials: store });
7
+ value.setProvider(openaiCodexProvider());
8
+ return value;
9
+ }
10
+
11
+ export async function login(store = new CodexCredentialStore(), interaction) {
12
+ await models(store).login(OPENAI_CODEX_PROVIDER, "oauth", interaction);
13
+ }
14
+ export async function logout(store = new CodexCredentialStore()) {
15
+ await store.delete(OPENAI_CODEX_PROVIDER);
16
+ }
17
+ export async function status(store = new CodexCredentialStore()) {
18
+ const credential = await store.read(OPENAI_CODEX_PROVIDER);
19
+ return credential?.type === "oauth" ? { authenticated: true, expiresAt: new Date(credential.expires) } : { authenticated: false };
20
+ }
21
+ export { authPath };
package/lib/index.js CHANGED
@@ -1,13 +1,14 @@
1
1
  import { createCodexAdapter } from "./adapter.js";
2
- import { PiCodexCredentialStore, OPENAI_CODEX_PROVIDER, PI_AUTH_FILE } from "./store.js";
2
+ import { CodexCredentialStore, OPENAI_CODEX_PROVIDER, authPath } from "./store.js";
3
+ export { login, logout, status } from "./auth.js";
3
4
 
4
5
  export const name = "llm-codex-oauth";
5
6
  export const inject = ["llm"];
6
7
 
7
- export { OPENAI_CODEX_PROVIDER, PI_AUTH_FILE, PiCodexCredentialStore, createCodexAdapter };
8
+ export { OPENAI_CODEX_PROVIDER, CodexCredentialStore, authPath, createCodexAdapter };
8
9
 
9
10
  export function apply(ctx) {
10
- const credentials = new PiCodexCredentialStore();
11
+ const credentials = new CodexCredentialStore();
11
12
  ctx.llm.registerAdapter(
12
13
  [OPENAI_CODEX_PROVIDER],
13
14
  createCodexAdapter(credentials, () => ctx.get?.("attachments")),
package/lib/store.js CHANGED
@@ -1,65 +1,60 @@
1
1
  import { mkdir, readFile, stat } from "node:fs/promises";
2
2
  import { homedir } from "node:os";
3
- import { dirname, join } from "node:path";
3
+ import { dirname, join, resolve } from "node:path";
4
4
  import { withFileLock, writeFileAtomic } from "@deepseek-ai/dsh-atomic-write";
5
5
 
6
6
  export const OPENAI_CODEX_PROVIDER = "openai-codex";
7
- export const PI_AUTH_FILE = join(homedir(), ".pi", "agent", "auth.json");
8
-
9
- function clone(value) {
10
- return value === undefined ? undefined : structuredClone(value);
7
+ export const OPENAI_CODEX_AUTH_FILE = ".openai-codex-auth.json";
8
+ export function authPath(home = process.env.DSH_HOME || join(homedir(), ".dsh")) {
9
+ return resolve(join(home, OPENAI_CODEX_AUTH_FILE));
11
10
  }
12
11
 
12
+ function clone(value) { return value === undefined ? undefined : structuredClone(value); }
13
13
  async function ownerOnly(filename) {
14
14
  if (process.platform === "win32") return;
15
15
  try {
16
16
  if ((await stat(filename)).mode & 0o077) throw new Error(`${filename} must be owner-only (chmod 600)`);
17
- } catch (error) {
18
- if (error?.code !== "ENOENT") throw error;
19
- }
17
+ } catch (error) { if (error?.code !== "ENOENT") throw error; }
18
+ }
19
+ function parse(text, filename) {
20
+ let value;
21
+ try { value = JSON.parse(text); } catch { throw new Error(`${filename} is not valid JSON`); }
22
+ const credential = value?.credential;
23
+ if (value?.version !== 1 || !credential || credential.type !== "oauth") throw new Error(`${filename} has an invalid auth format`);
24
+ for (const key of ["access", "refresh", "accountId"])
25
+ if (typeof credential[key] !== "string" || credential[key].length === 0) throw new Error(`${filename} credential is invalid`);
26
+ if (!Number.isFinite(credential.expires) || credential.expires <= 0) throw new Error(`${filename} expiry is invalid`);
27
+ return clone(credential);
20
28
  }
21
29
 
22
- export class PiCodexCredentialStore {
23
- constructor(filename = PI_AUTH_FILE) { this.filename = filename; }
24
-
25
- async readAll() {
30
+ export class CodexCredentialStore {
31
+ constructor(filename = authPath()) { this.filename = filename; }
32
+ async readCurrent() {
26
33
  await ownerOnly(this.filename);
27
- try { return JSON.parse(await readFile(this.filename, "utf8")); }
28
- catch (error) { if (error?.code === "ENOENT") return {}; throw error; }
29
- }
30
-
31
- async read(providerId) {
32
- if (providerId !== OPENAI_CODEX_PROVIDER) return undefined;
33
- const value = (await this.readAll())[providerId];
34
- return value?.type === "oauth" ? clone(value) : undefined;
35
- }
36
-
37
- async list() {
38
- return (await this.read(OPENAI_CODEX_PROVIDER) === undefined)
39
- ? [] : [{ providerId: OPENAI_CODEX_PROVIDER, type: "oauth" }];
34
+ try { return parse(await readFile(this.filename, "utf8"), this.filename); }
35
+ catch (error) { if (error?.code === "ENOENT") return undefined; throw error; }
40
36
  }
41
-
37
+ async read(providerId) { return providerId === OPENAI_CODEX_PROVIDER ? this.readCurrent() : undefined; }
38
+ async list() { return (await this.readCurrent()) ? [{ providerId: OPENAI_CODEX_PROVIDER, type: "oauth" }] : []; }
42
39
  async modify(providerId, fn) {
43
40
  if (providerId !== OPENAI_CODEX_PROVIDER) throw new Error(`credential store does not own ${providerId}`);
44
41
  await mkdir(dirname(this.filename), { recursive: true, mode: 0o700 });
45
42
  return withFileLock(this.filename, async () => {
46
- const auth = await this.readAll();
47
- const current = auth[providerId];
43
+ const current = await this.readCurrent();
48
44
  const next = await fn(clone(current));
49
- if (next === undefined) return clone(current);
50
- auth[providerId] = next;
51
- await writeFileAtomic(this.filename, `${JSON.stringify(auth, null, 2)}\n`, { mode: 0o600, dirMode: 0o700 });
45
+ if (next === undefined) return current;
46
+ const document = { version: 1, credential: next };
47
+ parse(JSON.stringify(document), this.filename);
48
+ await writeFileAtomic(this.filename, `${JSON.stringify(document, null, 2)}\n`, { mode: 0o600, dirMode: 0o700 });
52
49
  return clone(next);
53
50
  });
54
51
  }
55
-
56
52
  async delete(providerId) {
57
53
  if (providerId !== OPENAI_CODEX_PROVIDER) return;
58
54
  await mkdir(dirname(this.filename), { recursive: true, mode: 0o700 });
59
55
  await withFileLock(this.filename, async () => {
60
- const auth = await this.readAll();
61
- delete auth[providerId];
62
- await writeFileAtomic(this.filename, `${JSON.stringify(auth, null, 2)}\n`, { mode: 0o600, dirMode: 0o700 });
56
+ const { rm } = await import("node:fs/promises");
57
+ await rm(this.filename, { force: true });
63
58
  });
64
59
  }
65
60
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dsh-codex-oauth",
3
- "version": "0.1.1",
4
- "description": "Use Pi's OpenAI Codex OAuth subscription in DeepSeek Harness",
3
+ "version": "0.2.0",
4
+ "description": "Use a ChatGPT Codex subscription in DeepSeek Harness through standalone OAuth",
5
5
  "repository": {
6
6
  "type": "git",
7
7
  "url": "git+https://github.com/eons2long/dsh-codex-oauth.git"
@@ -15,8 +15,9 @@
15
15
  "scripts": { "test": "node test/smoke.mjs" },
16
16
  "type": "module",
17
17
  "main": "lib/index.js",
18
+ "bin": { "dsh-openai-codex": "bin/dsh-openai-codex.js" },
18
19
  "exports": { ".": "./lib/index.js" },
19
- "files": ["lib", "cordis.patch.yml", "README.md", "README.zh.md", "LICENSE"],
20
+ "files": ["lib", "bin", "cordis.patch.yml", "README.md", "README.zh.md", "SPEC.md", "LICENSE"],
20
21
  "dsh": { "bundle": { "patch": "./cordis.patch.yml" } },
21
22
  "peerDependencies": {
22
23
  "@earendil-works/pi-ai": "^0.84.1",