Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ jobs:
# Publish CLI to npm
# ----------------------------------------------------------------------
publish-cli:
name: Publish @deepcode/cli to npm
name: Publish @oratis/deepcode to npm
# Avoid a partial release: do not publish npm until both installable
# desktop/editor artifacts have built successfully.
needs: [validate, build-vscode, build-mac]
Expand Down
20 changes: 20 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,26 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
precise failure — a convention passed by word of mouth that had stopped being
enforced. They now share `gitSpawnEnv`, and a check fails the build if a test
spawns `git` without it.
- **The CLI is published as `@oratis/deepcode`.** 0.3.0 renamed it away from
`deepcode-cli` because that name belongs to an unrelated project — but
`@deepcode/cli` was not ours either. The leaf name is unpublished, which is
what made it look free; the `@deepcode` **scope** holds `@deepcode/tsc` and
`@deepcode/dcignore`, and npm rejects a publish into a scope you do not own. A
scope is not claimable by publishing into it, so `pnpm publish` would have
returned the same 403 the rename was meant to fix. The new name is the
repository owner's personal scope, which needs no organisation to exist first.
Nothing was ever published under either old name, so no installed package
changes. The binary is still `deepcode`.
- A test now asserts that every `npm i -g …` in a current document or in CLI
source names the package `apps/cli/package.json` publishes. Both renames so far
moved some install strings and left others behind. It scans the whole
repository minus an explicit list of historical snapshots, rather than an
allowlist of the documents somebody thought of — an allowlist has to be
extended by whoever adds the next document, and stays silent when they forget,
which is the same shape as the bug it is there to catch.
- `apps/cli/README.md` — the npm landing page — still described the CLI as an
"M0 骨架,命令入口存在但不能用" and pointed at milestone numbers for when
features would arrive. It shipped in the package `files` list.

## [0.3.0] — 2026-08-08

Expand Down
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ deepcode/
│ ├── core/ # @deepcode/core — 内核(无 UI 依赖)
│ └── shared-ui/ # CLI 与桌面客户端共享类型
├── apps/
│ ├── cli/ # @deepcode/cli — npm 包,命令 `deepcode`
│ ├── cli/ # @oratis/deepcode — npm 包,命令 `deepcode`
│ └── desktop/ # Mac 客户端(Tauri 2 + Rust + React)
├── docs/
│ ├── DEVELOPMENT_PLAN.md
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ DeepCode 让 DeepSeek 可以在本地代码库中执行读取、编辑、命令

```bash
# 1. 装 CLI
npm i -g @deepcode/cli
npm i -g @oratis/deepcode

# 2. 设 DeepSeek key(首次启动会引导)
deepcode
Expand Down Expand Up @@ -83,7 +83,7 @@ packages/
core/ # @deepcode/core — agent loop, providers, tools, MCP, sandbox, hooks (UI-agnostic)
shared-ui/ # @deepcode/shared-ui — types shared between CLI + Mac client + VS Code
apps/
cli/ # @deepcode/cli — Node.js CLI (npm publishable)
cli/ # @oratis/deepcode — Node.js CLI (npm publishable)
desktop/ # @deepcode/desktop — Tauri 2 + React Mac client
vscode/ # deepcode — VS Code extension (app-server protocol client)
lsp/ # @deepcode/lsp — LSP bridge for Neovim/Emacs/Sublime (v1.1)
Expand Down
44 changes: 33 additions & 11 deletions apps/cli/README.md
Original file line number Diff line number Diff line change
@@ -1,21 +1,43 @@
# @deepcode/cli
# @oratis/deepcode

DeepCode CLI — Node.js 一键安装的命令行 AI 编程代理。
DeepCode CLI —— DeepSeek 驱动的命令行编程 agent,在真实代码库里读文件、改代码、跑命令、
审阅 diff,并把每一次改动记账。

## Install (post-v1)
## 安装

```bash
npm i -g @deepcode/cli
npm i -g @oratis/deepcode
deepcode --help
```

## 当前状态
需要 Node 22+。首次启动会引导填入 `DEEPSEEK_API_KEY`。

M0 骨架 — 命令入口存在但不能用。实际功能:
## 用法

- onboarding + REPL:M2
- 30+ slash commands:M2
- 完整 17 个 CLI flags:M2-M5
- headless `-p` 模式:M8
```bash
deepcode # 交互式 REPL
deepcode -p "fix the bug in src/auth.ts" # headless 一次性
deepcode --mode plan # 只读规划,出计划等批准
deepcode --model deepseek-reasoner --effort high
```

工具、权限、沙箱、MCP、skills、plugins、hooks、后台任务与定时任务的完整说明见
[quickstart](https://github.com/oratis/deepcode/blob/main/docs/quickstart.md) 与
[CLI flags](https://github.com/oratis/deepcode/blob/main/docs/cli-flags.md)。

## 治理

- [`deepcode contract`](https://github.com/oratis/deepcode/blob/main/docs/file-contract.md)
—— 路径维度的读/写/执行契约,与既有权限规则按"最严者胜"合成,只能收紧。
- [`deepcode ledger`](https://github.com/oratis/deepcode/blob/main/docs/change-ledger.md)
—— 变更账本:每次改动配上驱动它的请求和可回滚的检查点。
- `deepcode doctor` —— 打印运行时能力声明与契约告警。

## 关于包名

二进制始终是 `deepcode`。npm 上的包名换过两次:`deepcode-cli` 与 `@deepcode/cli`
都属于无关的第三方项目,无法发布。当前且长期的名字是 **`@oratis/deepcode`**。

## License

详见 `docs/DEVELOPMENT_PLAN.md` §5 / §5a。
MIT
2 changes: 1 addition & 1 deletion apps/cli/package.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"name": "@deepcode/cli",
"name": "@oratis/deepcode",
"version": "0.3.0",
"description": "DeepCode CLI — DeepSeek-powered AI coding agent for real codebases",
"license": "MIT",
Expand Down
2 changes: 1 addition & 1 deletion apps/cli/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ async function main(): Promise<number> {
return doctor();
}
if (args.upgrade) {
process.stdout.write(`Run: npm i -g @deepcode/cli@latest\n`);
process.stdout.write(`Run: npm i -g @oratis/deepcode@latest\n`);
process.stdout.write(`(The Mac client updates itself; only the CLI needs this.)\n`);
return 0;
}
Expand Down
2 changes: 1 addition & 1 deletion apps/cli/src/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1151,7 +1151,7 @@ export const UpgradeCommand: SlashCommand = {
run() {
return [
`DeepCode CLI v${VERSION}`,
'Update the CLI: npm i -g @deepcode/cli@latest',
'Update the CLI: npm i -g @oratis/deepcode@latest',
'The macOS desktop app auto-updates via GitHub Releases.',
];
},
Expand Down
2 changes: 1 addition & 1 deletion apps/cli/src/parity-commands.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ describe('/upgrade + /privacy-settings', () => {
it('/upgrade shows the version + update instructions', async () => {
const out = (await reg.match('/upgrade')!.cmd.run([], ctx())).join('\n');
expect(out).toMatch(/DeepCode CLI v\d/);
expect(out).toContain('npm i -g @deepcode/cli@latest');
expect(out).toContain('npm i -g @oratis/deepcode@latest');
});

it('/privacy-settings shows data locations + the DeepSeek endpoint', async () => {
Expand Down
2 changes: 1 addition & 1 deletion docs/MIGRATION_FROM_CLAUDE_CODE.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ The copy below is only needed if you want DeepCode to own its own copies.

```bash
# 1. Install DeepCode CLI
npm install -g @deepcode/cli
npm install -g @oratis/deepcode

# 2. Set your DeepSeek key
mkdir -p ~/.deepcode
Expand Down
24 changes: 18 additions & 6 deletions docs/RELEASING.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ ignore red releases. A job that actually _fails_ still blocks the release.
| `APPLE_TEAM_ID` | 10-character team ID (from developer.apple.com → membership) |
| `CSC_LINK` | Base64-encoded `.p12` of the Developer ID Application cert |
| `CSC_KEY_PASSWORD` | Password used when exporting the `.p12` |
| `NPM_TOKEN` | npm access token with `publish` scope for the `@deepcode` scope |
| `NPM_TOKEN` | npm access token able to publish into the `@oratis` scope |

### 2. Export the Developer ID certificate

Expand Down Expand Up @@ -60,10 +60,22 @@ App-specific passwords → Generate. Save the 16-char password as
[npmjs.com](https://www.npmjs.com) → account → Access Tokens → Generate
new token → **Automation** (CI-friendly) → save as `NPM_TOKEN`.

The CLI publishes as **`@deepcode/cli`**. The unscoped `deepcode-cli` on npm
belongs to an unrelated project, so the `@deepcode` org must exist and the token
must be able to publish into it. The workflow already passes `--access public`,
which scoped packages need in order not to default to private.
The CLI publishes as **`@oratis/deepcode`** — the personal scope of the account
that owns the repository, so no organisation has to exist first. The token must
be issued by that account. The workflow already passes `--access public`, which
scoped packages need in order not to default to private.

Two earlier names were dead ends, and both were only discovered by checking the
registry rather than by reading our own `package.json`:

| Name | Owner |
| -------------- | ---------------------------------------------------------------- |
| `deepcode-cli` | an unrelated Doubao-based CLI (`guocong-bincai/deepcode-cli`) |
| `@deepcode/*` | an unrelated scope holding `@deepcode/tsc`, `@deepcode/dcignore` |

`@deepcode/cli` itself is unpublished, which is what made it look available —
but npm rejects a publish into a scope you do not own regardless of whether the
individual name is free. A scope is not claimable by publishing into it.

## Releasing

Expand Down Expand Up @@ -168,7 +180,7 @@ download manually; the "Relaunch to update" flow lights up once the feed exists.

## After a release

- Verify: `npm view @deepcode/cli@<version>` shows the new version
- Verify: `npm view @oratis/deepcode@<version>` shows the new version
- Verify: `https://github.com/oratis/deepcode/releases/tag/v<version>`
has the DMG and version-matched VSIX attached
- Optional: announce in the README / homepage
Expand Down
6 changes: 6 additions & 0 deletions docs/V0.3.0_REPORT.md
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,12 @@ committed 哈希能防住"nodejs.org 被攻陷",构建时抓 `SHASUMS256.txt`
历史快照文档(MORNING_REPORT / DEVELOPMENT_PLAN / HANDOFF / BEHAVIOR_PARITY)保留旧名,
它们记录的是写下时为真的事。

> **后续更正(#253)**:`@deepcode/cli` 同样不是我们的。leaf name 未被发布过,
> 这正是它看起来可用的原因——但 `@deepcode` **scope** 下有 `@deepcode/tsc` 与
> `@deepcode/dcignore`,npm 不允许发布进你不拥有的 scope,scope 也无法靠发布占用。
> 本节记录的是当时的决定;实际发布名是 **`@oratis/deepcode`**,二进制仍为 `deepcode`。
> 两个旧名都从未发布过,所以没有任何已安装的包受影响。

> typecheck 抓到一个真问题:`/npm i -g @deepcode/cli@latest/` 里 scope 的 `/` 会提前终结正则字面量。
> 已改为 `toContain`。带 scope 的重命名撞上正则,正是全局替换会漏掉的那类。

Expand Down
2 changes: 1 addition & 1 deletion docs/cli-flags.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ deepcode upgrade # CLI self-update
| `-h`, `--help` | Print usage | M2 ✅ |
| `-v`, `--version` | Print version | M2 ✅ |
| `doctor` | Health check (runtime, credentials, config provenance/trust/issues) | M2 ✅ |
| `upgrade` | Print `npm i -g @deepcode/cli@latest` hint | M2 ✅ |
| `upgrade` | Print `npm i -g @oratis/deepcode@latest` hint | M2 ✅ |
| `-p`, `--print <prompt>` | Headless one-shot | M8 |

## Session shaping
Expand Down
2 changes: 1 addition & 1 deletion docs/quickstart.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ permission, and recovery behavior is still being unified; see the

```bash
# 1. Install
npm i -g @deepcode/cli
npm i -g @oratis/deepcode

# 2. Start the REPL — the first run walks you through setting your DeepSeek key
deepcode
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/config/claude-compat.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
//
// The migration guide asked people to `mv ~/.claude/... ~/.deepcode/...` before
// DeepCode would see anything they had. That is five steps of grit in front of
// `npm i -g @deepcode/cli && deepcode`, and it is the first thing a migrating
// `npm i -g @oratis/deepcode && deepcode`, and it is the first thing a migrating
// user hits.

import { mkdir, mkdtemp, writeFile } from 'node:fs/promises';
Expand Down
2 changes: 1 addition & 1 deletion scripts/install-cron-daemon.sh
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ if [[ -z "$BIN" ]]; then
BIN="$ROOT/apps/cli/dist/index.js"
else
echo "ERROR: could not find a deepcode binary."
echo "Run 'pnpm --filter deepcode-cli build' or set DEEPCODE_BIN=/path/to/deepcode."
echo "Run 'pnpm --filter @oratis/deepcode build' or set DEEPCODE_BIN=/path/to/deepcode."
exit 1
fi
fi
Expand Down
105 changes: 105 additions & 0 deletions scripts/package-name-consistency.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
// The name we tell people to install must be the name we publish.
//
// The CLI has been renamed twice, and both times some of the install strings
// moved while others stayed. A rename that touches `package.json` but not the
// `/upgrade` hint ships a working package alongside instructions for a package
// that belongs to somebody else — which is worse than either name alone.
//
// So: every `npm i -g <pkg>` in a current document or in CLI source must name
// the package `apps/cli/package.json` publishes.

import { lstatSync, readdirSync, readFileSync } from 'node:fs';
import { join, relative, resolve } from 'node:path';
import { describe, expect, it } from 'vitest';

const root = resolve(import.meta.dirname, '..');
const read = (path: string): string => readFileSync(resolve(root, path), 'utf8');

const publishedName = (): string => JSON.parse(read('apps/cli/package.json')).name as string;

// Everything is scanned unless it is listed here, rather than nothing being
// scanned unless it is listed there.
//
// An allowlist has to be extended by whoever adds the next document, and the
// failure when they forget is silent — which is the same shape as the bug this
// file exists to catch. It is also not hypothetical: the rename this test
// shipped with had to hand-edit install strings in
// `packages/core/src/config/claude-compat.test.ts` and
// `apps/cli/src/parity-commands.test.ts`, neither of which an allowlist built
// from the documents anybody thought of would have contained.
//
// Historical snapshots deliberately keep the names that were true when they
// were written; `scripts/check-docs.mjs` is what keeps them marked as such, and
// this is the same list. CHANGELOG entries for shipped releases are history too.
const historical = new Set([
'CHANGELOG.md',
'MORNING_REPORT.md',
'docs/HANDOFF.md',
'docs/BEHAVIOR_PARITY.md',
'docs/DEVELOPMENT_PLAN.md',
// A dated delivery report for one release. Its §7.1 records the rename that
// was decided at the time, and that record should keep saying what was
// decided — with a note pointing at the correction, which is what it has.
'docs/V0.3.0_REPORT.md',
]);
const skipDirs = new Set(['node_modules', 'dist', 'target', 'out', '.git', 'release-artifacts']);
const scannedExtensions = ['.md', '.ts', '.tsx', '.mjs', '.sh'];

function scannedFiles(dir: string, found: string[] = []): string[] {
for (const entry of readdirSync(dir)) {
if (skipDirs.has(entry)) continue;
const path = join(dir, entry);
// lstat, not stat: a symlink into a skipped tree must not be followed back
// in, and a broken one must not throw.
const stats = lstatSync(path, { throwIfNoEntry: false });
if (!stats) continue;
if (stats.isDirectory()) scannedFiles(path, found);
else if (scannedExtensions.some((ext) => entry.endsWith(ext))) {
const rel = relative(root, path);
if (!historical.has(rel)) found.push(rel);
}
}
return found;
}

const installCommand = /npm (?:i|install) -g\s+(@?[\w./-]+?)(?:@latest)?(?=[\s`'"\\]|$)/g;

describe('published package name', () => {
it('is what every install instruction names', () => {
const expected = publishedName();
const wrong: string[] = [];

for (const path of scannedFiles(root)) {
for (const match of read(path).matchAll(installCommand)) {
if (match[1] !== expected) wrong.push(`${path}: ${match[0]}`);
}
}

expect(wrong).toEqual([]);
});

it('scans the files that actually carry install strings', () => {
// Guards the walker itself: a skip rule that quietly swallowed `docs/` or
// `apps/` would leave the check above passing over nothing at all.
const scanned = scannedFiles(root);
expect(scanned).toContain('README.md');
expect(scanned).toContain('docs/quickstart.md');
expect(scanned).toContain('apps/cli/src/cli.ts');
expect(scanned).toContain('packages/core/src/config/claude-compat.test.ts');
expect(scanned).not.toContain('docs/DEVELOPMENT_PLAN.md');
});

it('is not one of the names that belong to somebody else', () => {
// Checked against the registry, not against availability of the leaf name:
// `@deepcode/cli` is unpublished, which is exactly why it looked free — but
// the `@deepcode` scope holds `@deepcode/tsc` and `@deepcode/dcignore`, and
// npm rejects a publish into a scope you do not own. `deepcode-cli` is an
// unrelated Doubao-based CLI. Both would 403 at `pnpm publish`.
expect(['deepcode-cli', '@deepcode/cli']).not.toContain(publishedName());
});

it('is reached by a binary still called deepcode', () => {
const pkg = JSON.parse(read('apps/cli/package.json')) as { bin: Record<string, string> };
expect(Object.keys(pkg.bin)).toEqual(['deepcode']);
});
});
Loading