@este.systems/dsc 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 +408 -0
- package/bin/dsc.mjs +29 -0
- package/dist/agent.js +259 -0
- package/dist/agent.js.map +1 -0
- package/dist/api.js +333 -0
- package/dist/api.js.map +1 -0
- package/dist/approval.js +79 -0
- package/dist/approval.js.map +1 -0
- package/dist/audit.js +26 -0
- package/dist/audit.js.map +1 -0
- package/dist/compact.js +100 -0
- package/dist/compact.js.map +1 -0
- package/dist/history.js +212 -0
- package/dist/history.js.map +1 -0
- package/dist/index.js +830 -0
- package/dist/index.js.map +1 -0
- package/dist/markdown.js +543 -0
- package/dist/markdown.js.map +1 -0
- package/dist/prompt.js +44 -0
- package/dist/prompt.js.map +1 -0
- package/dist/repl_history.js +55 -0
- package/dist/repl_history.js.map +1 -0
- package/dist/search.js +215 -0
- package/dist/search.js.map +1 -0
- package/dist/tools.js +670 -0
- package/dist/tools.js.map +1 -0
- package/dist/ui.js +165 -0
- package/dist/ui.js.map +1 -0
- package/package.json +57 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Este Systems
|
|
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,408 @@
|
|
|
1
|
+
# dsc
|
|
2
|
+
|
|
3
|
+
A CLI coding agent for [DeepSeek](https://api-docs.deepseek.com/).
|
|
4
|
+
Streams responses, calls tools (`bash`, `read_file`, `write_file`, `edit_file`,
|
|
5
|
+
`grep`, `glob`, `web_fetch`, `web_search`), keeps per-cwd sessions, and runs
|
|
6
|
+
in your terminal as a plain readline REPL — output stays selectable / pasteable
|
|
7
|
+
and approvals happen inline.
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
dsc requires **Node 22+** (it uses `fs.promises.glob`, stable since 22.0).
|
|
12
|
+
|
|
13
|
+
### Step 1 — install Node 22+
|
|
14
|
+
|
|
15
|
+
| Platform | One-liner |
|
|
16
|
+
|---|---|
|
|
17
|
+
| **Linux (Debian/Ubuntu)** | `curl -fsSL https://deb.nodesource.com/setup_22.x \| sudo -E bash - && sudo apt install -y nodejs` |
|
|
18
|
+
| **Linux (Fedora/RHEL)** | `sudo dnf install -y nodejs:22/common` |
|
|
19
|
+
| **Linux (Arch)** | `sudo pacman -S nodejs npm` |
|
|
20
|
+
| **macOS** | `brew install node@22 && brew link node@22` |
|
|
21
|
+
| **Windows (winget)** | `winget install OpenJS.NodeJS.LTS` |
|
|
22
|
+
| **Windows (scoop)** | `scoop install nodejs-lts` |
|
|
23
|
+
| **Cross-platform (nvm)** | `nvm install 22 && nvm use 22` |
|
|
24
|
+
|
|
25
|
+
Verify: `node --version` should print `v22.x.x` or higher.
|
|
26
|
+
|
|
27
|
+
### Step 2 — install dsc
|
|
28
|
+
|
|
29
|
+
Three options. Once the package is published to npm, **(A)** is what you want.
|
|
30
|
+
Today, use **(B)** for a frozen tarball or **(C)** if you'll be hacking on the
|
|
31
|
+
source.
|
|
32
|
+
|
|
33
|
+
**(A) From npm** *(after publish)*:
|
|
34
|
+
|
|
35
|
+
```sh
|
|
36
|
+
npm install -g @este.systems/dsc
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
**(B) From a local tarball** *(works on all platforms with the same npm)*:
|
|
40
|
+
|
|
41
|
+
```sh
|
|
42
|
+
git clone https://github.com/EsteSystems/dsc.git
|
|
43
|
+
cd dsc
|
|
44
|
+
npm install
|
|
45
|
+
npm run package # produces pkg/<name>-<version>.tgz
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
then on **Linux / macOS**:
|
|
49
|
+
|
|
50
|
+
```sh
|
|
51
|
+
scripts/install.sh
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
or on **Windows PowerShell**:
|
|
55
|
+
|
|
56
|
+
```powershell
|
|
57
|
+
.\scripts\install.ps1
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Both wrappers just call `npm install -g pkg/*.tgz` after auto-finding the
|
|
61
|
+
tarball.
|
|
62
|
+
|
|
63
|
+
**(C) From source for development** *(live edits, no rebuild step)*:
|
|
64
|
+
|
|
65
|
+
```sh
|
|
66
|
+
git clone https://github.com/EsteSystems/dsc.git
|
|
67
|
+
cd dsc
|
|
68
|
+
npm install
|
|
69
|
+
npm link # exposes `dsc` globally
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
The shim in `bin/dsc.mjs` runs the TypeScript sources directly through
|
|
73
|
+
`tsx`, so your next `dsc` launch picks up edits immediately. If `tsx`
|
|
74
|
+
isn't available it falls back to `dist/` (populate with `npm run build`).
|
|
75
|
+
|
|
76
|
+
### Step 3 — verify
|
|
77
|
+
|
|
78
|
+
```sh
|
|
79
|
+
dsc --help
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
If you get "command not found", your shell's `PATH` doesn't include npm's
|
|
83
|
+
global-bin directory. Find it with `npm config get prefix`; the binary
|
|
84
|
+
lives in `<prefix>/bin` on Linux/macOS or `<prefix>` on Windows. Add that
|
|
85
|
+
to your `PATH` and reopen the terminal.
|
|
86
|
+
|
|
87
|
+
## API key
|
|
88
|
+
|
|
89
|
+
dsc reads its DeepSeek key from `~/.config/deepseek/deepseek.json` on every
|
|
90
|
+
platform — Node's `os.homedir()` resolves to `C:\Users\<you>` on Windows, so
|
|
91
|
+
the actual file path is `C:\Users\<you>\.config\deepseek\deepseek.json`. Set
|
|
92
|
+
`XDG_CONFIG_HOME` if you'd rather put it elsewhere.
|
|
93
|
+
|
|
94
|
+
**Linux / macOS:**
|
|
95
|
+
|
|
96
|
+
```sh
|
|
97
|
+
mkdir -p ~/.config/deepseek
|
|
98
|
+
cat > ~/.config/deepseek/deepseek.json <<'JSON'
|
|
99
|
+
{
|
|
100
|
+
"api_key": "sk-..."
|
|
101
|
+
}
|
|
102
|
+
JSON
|
|
103
|
+
chmod 600 ~/.config/deepseek/deepseek.json
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
**Windows PowerShell:**
|
|
107
|
+
|
|
108
|
+
```powershell
|
|
109
|
+
$dir = "$HOME\.config\deepseek"
|
|
110
|
+
New-Item -ItemType Directory -Force -Path $dir | Out-Null
|
|
111
|
+
'{"api_key":"sk-..."}' | Set-Content -Path "$dir\deepseek.json" -Encoding utf8
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Accepted shapes:
|
|
115
|
+
|
|
116
|
+
```jsonc
|
|
117
|
+
{ "api_key": "sk-..." } // simple
|
|
118
|
+
{ "DEEPSEEK_API_KEY": "sk-..." } // alt key
|
|
119
|
+
{ "env": { "DEEPSEEK_API_KEY": "sk-..." } } // env-style
|
|
120
|
+
{ "env": { "ANTHROPIC_AUTH_TOKEN": "sk-..." } } // claude-switcher compat
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
The env var `DEEPSEEK_API_KEY` takes priority over the file when set.
|
|
124
|
+
|
|
125
|
+
## Quick start
|
|
126
|
+
|
|
127
|
+
```sh
|
|
128
|
+
dsc # interactive REPL
|
|
129
|
+
dsc "summarize src/api.ts" # one-shot
|
|
130
|
+
dsc --yolo "rename Foo to Bar" # skip approval prompts
|
|
131
|
+
dsc -m deepseek-v4-flash # pick a model
|
|
132
|
+
dsc --no-resume # fresh session, ignore prior history
|
|
133
|
+
dsc --resume <id> # resume a specific session id
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## REPL commands
|
|
137
|
+
|
|
138
|
+
| Command | What it does |
|
|
139
|
+
|---|---|
|
|
140
|
+
| `/clear` | Start a new session. Old session stays on disk. |
|
|
141
|
+
| `/cost` | Show token usage and estimated cost so far. |
|
|
142
|
+
| `/model [name]` | Show or switch model. Available: `deepseek-v4-pro`, `deepseek-v4-flash`. |
|
|
143
|
+
| `/yolo` | Toggle approval mode (write/edit/bash/web_fetch). |
|
|
144
|
+
| `/reasoning [on\|off]` | Show/hide `reasoning_content` from thinking models. Default on. |
|
|
145
|
+
| `/list` | List sessions in the current cwd. The active session is marked with `*`. |
|
|
146
|
+
| `/resume <#\|id\|last>` | Resume a session by index (from `/list`), id, or `last`. |
|
|
147
|
+
| `/audit` | Print the path of the JSONL audit log. |
|
|
148
|
+
| `/transcript` | Print the full conversation, including any messages compaction archived. |
|
|
149
|
+
| `/compact [N]` | Summarize older turns into a synthetic block (kept in the system prompt) and move them to the archive. Keeps the last `N` user turns verbatim (default 4). Cumulative across re-runs. |
|
|
150
|
+
| `/edit [text]` | Open `$VISUAL`/`$EDITOR`/`vi` on a tmp file; the saved content runs as the next prompt. |
|
|
151
|
+
| `/exit` | Quit. |
|
|
152
|
+
|
|
153
|
+
### Multi-line input
|
|
154
|
+
|
|
155
|
+
End a line with a single `\` to continue on the next line (bash-style).
|
|
156
|
+
`\\` is treated as a literal trailing backslash. For longer or paste-heavy
|
|
157
|
+
drafts, use `/edit`.
|
|
158
|
+
|
|
159
|
+
```
|
|
160
|
+
> please write a function\
|
|
161
|
+
… that takes (a, b, c)\
|
|
162
|
+
… and returns a + b + c
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### Hotkeys
|
|
166
|
+
|
|
167
|
+
`Ctrl+C` aborts the current turn (first press), exits if pressed again
|
|
168
|
+
within 1 second. `Ctrl+D` exits cleanly. Up / down arrows recall past
|
|
169
|
+
prompts (persisted across sessions).
|
|
170
|
+
|
|
171
|
+
### Session scoping (per-directory)
|
|
172
|
+
|
|
173
|
+
Sessions are tied to the directory you launched dsc from — that's how
|
|
174
|
+
auto-resume figures out which conversation to bring back.
|
|
175
|
+
|
|
176
|
+
- Run `dsc` in `~/code/foo` → it auto-resumes the most recent session
|
|
177
|
+
whose `cwd` is `~/code/foo`. If there isn't one, you start fresh.
|
|
178
|
+
- `cd` into `~/code/bar` and run `dsc` → you get bar's last session,
|
|
179
|
+
not foo's. Project conversations stay scoped to their project; you
|
|
180
|
+
can't accidentally bleed astrophysics notes into a CLI refactor.
|
|
181
|
+
- `/clear` starts a brand-new session **for the current cwd**. The old
|
|
182
|
+
one stays on disk and shows up in `/list` next time you're back.
|
|
183
|
+
- `/list` shows only sessions whose `cwd` matches the current
|
|
184
|
+
directory. `/resume <#>` resolves indices from that list.
|
|
185
|
+
- `/resume <id>` (or a `/save`'d name) **can** cross cwds — useful
|
|
186
|
+
if you want to revisit a session from elsewhere; the cwd it was
|
|
187
|
+
born in stays attached to it.
|
|
188
|
+
- `--no-resume` skips auto-resume entirely and gives you a fresh
|
|
189
|
+
session this launch.
|
|
190
|
+
|
|
191
|
+
Each session is a single JSON file under
|
|
192
|
+
`~/.local/share/dsc/sessions/`. Open one in your editor if you want to
|
|
193
|
+
see exactly what got persisted, including any compacted summary and
|
|
194
|
+
the archived messages from `/transcript`.
|
|
195
|
+
|
|
196
|
+
## Tools the agent can use
|
|
197
|
+
|
|
198
|
+
| Tool | Approval | Notes |
|
|
199
|
+
|---|---|---|
|
|
200
|
+
| `read_file(path, offset?, limit?)` | none | 2000 lines default; long lines truncated. |
|
|
201
|
+
| `grep(pattern, path?, glob?, case_insensitive?)` | none | ripgrep when available, `grep -rn` fallback. |
|
|
202
|
+
| `glob(pattern, path?)` | none | Node 22+ `fs.glob`, capped at 500. |
|
|
203
|
+
| `web_search(query, count?, freshness?)` | none | Pluggable backends (Brave / Tavily / DuckDuckGo). |
|
|
204
|
+
| `write_file(path, content)` | yes (unless `--yolo`) | Side-by-side diff in the prompt. |
|
|
205
|
+
| `edit_file(path, old_string, new_string, replace_all?)` | yes | Exact substring replace; old_string must be unique unless `replace_all=true`. |
|
|
206
|
+
| `bash(command, description?, timeout_ms?)` | yes | `/bin/sh -c`, output capped at 16 KB. |
|
|
207
|
+
| `web_fetch(url)` | yes | HTML stripped to text, capped at 50 KB. |
|
|
208
|
+
|
|
209
|
+
Read-only tools never prompt. The rest do unless `--yolo` is on.
|
|
210
|
+
|
|
211
|
+
## Sessions and history
|
|
212
|
+
|
|
213
|
+
Each session is a JSON file under `$XDG_DATA_HOME/dsc/sessions/`
|
|
214
|
+
(default `~/.local/share/dsc/sessions/`) keyed by id. It carries:
|
|
215
|
+
|
|
216
|
+
- `messages` — the active conversation log (sent to the API).
|
|
217
|
+
- `archivedMessages` — older messages that `/compact` has summarized away.
|
|
218
|
+
Persisted on disk for `/transcript`, never sent to the API.
|
|
219
|
+
- `compaction` — the cumulative summary text and metadata.
|
|
220
|
+
- `stats` — token / cost / tool-call counters.
|
|
221
|
+
- `model` — last selected model.
|
|
222
|
+
|
|
223
|
+
Saves happen on every `onTurn` callback (after each assistant message,
|
|
224
|
+
after each tool result), with a single-in-flight, coalescing writer —
|
|
225
|
+
so a Ctrl+C / OOM / power loss mid-turn won't cost you the latest
|
|
226
|
+
committed state.
|
|
227
|
+
|
|
228
|
+
## Compaction
|
|
229
|
+
|
|
230
|
+
`/compact [N]` summarizes everything before the last `N` user turns,
|
|
231
|
+
stores the summary on the session, archives the original messages, and
|
|
232
|
+
trims `messages` to the kept tail. The summary appears in the dynamic
|
|
233
|
+
suffix of every subsequent system prompt (`Previously in this session:`),
|
|
234
|
+
so the model retains semantic context. Cumulative — re-running `/compact`
|
|
235
|
+
folds the prior summary into the new one.
|
|
236
|
+
|
|
237
|
+
Auto-compact runs the same routine after any successful turn whose
|
|
238
|
+
estimated context exceeds `DSC_AUTO_COMPACT_AT` tokens (default 50 000;
|
|
239
|
+
set `0` / `off` / `false` to disable).
|
|
240
|
+
|
|
241
|
+
`/transcript` prints the full conversation, including archived chunks,
|
|
242
|
+
so nothing is lost — just absent from the prompt the model sees.
|
|
243
|
+
|
|
244
|
+
## Audit log
|
|
245
|
+
|
|
246
|
+
Every tool execution (including rejected ones) writes one JSONL line to
|
|
247
|
+
`$XDG_STATE_HOME/dsc/audit.log` (default `~/.local/state/dsc/audit.log`).
|
|
248
|
+
Each line carries `ts`, `session`, `cwd`, `tool`, `approved`, plus
|
|
249
|
+
tool-specific fields:
|
|
250
|
+
|
|
251
|
+
```json
|
|
252
|
+
{"ts":"2026-05-09T15:32:01Z","session":"…","cwd":"/home/dann/code/dsc","tool":"bash","approved":true,"command":"npm test","exit":0,"stdout_bytes":4012,"stderr_bytes":0}
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
Useful greps:
|
|
256
|
+
|
|
257
|
+
```sh
|
|
258
|
+
# every bash command this week
|
|
259
|
+
jq 'select(.tool=="bash") | "\(.ts) \(.command)"' ~/.local/state/dsc/audit.log
|
|
260
|
+
|
|
261
|
+
# things rejected at approval
|
|
262
|
+
jq 'select(.approved==false)' ~/.local/state/dsc/audit.log
|
|
263
|
+
|
|
264
|
+
# files written by a specific session
|
|
265
|
+
jq 'select(.session=="abc1234" and .tool=="write_file") | .path' \
|
|
266
|
+
~/.local/state/dsc/audit.log
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
Disable with `DSC_NO_AUDIT=1`. There's no rotation — at gigabyte scale
|
|
270
|
+
you'll want to truncate it yourself.
|
|
271
|
+
|
|
272
|
+
## File locations
|
|
273
|
+
|
|
274
|
+
| Path | What |
|
|
275
|
+
|---|---|
|
|
276
|
+
| `~/.config/deepseek/deepseek.json` | API key (and search-provider keys). 0600. |
|
|
277
|
+
| `~/.local/share/dsc/sessions/<id>.json` | One file per session. |
|
|
278
|
+
| `~/.local/state/dsc/history` | Up/down arrow recall (1000-line cap). |
|
|
279
|
+
| `~/.local/state/dsc/audit.log` | JSONL, append-only. |
|
|
280
|
+
| `/tmp/dsc-edit-*/prompt.md` | Transient; created by `/edit`, removed on close. |
|
|
281
|
+
| `<repo>/dist/` | Compiled output (only used as a fallback for the global shim). |
|
|
282
|
+
|
|
283
|
+
XDG variables observed: `XDG_CONFIG_HOME`, `XDG_STATE_HOME`, `XDG_DATA_HOME`.
|
|
284
|
+
|
|
285
|
+
## Environment variables
|
|
286
|
+
|
|
287
|
+
| Var | Default | Purpose |
|
|
288
|
+
|---|---|---|
|
|
289
|
+
| `DEEPSEEK_API_KEY` | (read from config file) | Overrides the `api_key` in `deepseek.json`. |
|
|
290
|
+
| `DSC_AUTO_COMPACT_AT` | `50000` | Token threshold for auto-compact. `0`/`off`/`false` disables. |
|
|
291
|
+
| `DSC_NO_AUDIT` | (off) | `1` disables the JSONL audit log. |
|
|
292
|
+
| `DSC_SEARCH_PROVIDER` | (config or `ddg`) | `brave`, `tavily`, or `ddg`. |
|
|
293
|
+
| `BRAVE_API_KEY` | (config) | Brave Search key. |
|
|
294
|
+
| `TAVILY_API_KEY` | (config) | Tavily key. |
|
|
295
|
+
| `VISUAL` / `EDITOR` | (vi) | Used by `/edit`. |
|
|
296
|
+
| `XDG_CONFIG_HOME` | `~/.config` | Config root. |
|
|
297
|
+
| `XDG_STATE_HOME` | `~/.local/state` | State root. |
|
|
298
|
+
| `XDG_DATA_HOME` | `~/.local/share` | Data root. |
|
|
299
|
+
|
|
300
|
+
## Search providers
|
|
301
|
+
|
|
302
|
+
Pick at runtime with `DSC_SEARCH_PROVIDER`:
|
|
303
|
+
|
|
304
|
+
- **brave** — [api-dashboard.search.brave.com](https://api-dashboard.search.brave.com), 2000 free queries/month. Recommended.
|
|
305
|
+
- **tavily** — [tavily.com](https://tavily.com), 1000 free/month, agent-tuned snippets.
|
|
306
|
+
- **ddg** — DuckDuckGo HTML scrape, no key, brittle.
|
|
307
|
+
|
|
308
|
+
Per-provider keys live in the config:
|
|
309
|
+
|
|
310
|
+
```jsonc
|
|
311
|
+
{
|
|
312
|
+
"api_key": "sk-...",
|
|
313
|
+
"search": {
|
|
314
|
+
"provider": "brave",
|
|
315
|
+
"brave": { "api_key": "BSA..." }
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
`{PROVIDER}_API_KEY` env var (e.g. `BRAVE_API_KEY`) overrides the
|
|
321
|
+
file value.
|
|
322
|
+
|
|
323
|
+
## Packaging and distribution
|
|
324
|
+
|
|
325
|
+
For local global install (any platform with Node 22+):
|
|
326
|
+
|
|
327
|
+
```sh
|
|
328
|
+
npm run package # produces pkg/<name>-<version>.tgz
|
|
329
|
+
scripts/install.sh # linux / macOS — wraps `npm install -g pkg/*.tgz`
|
|
330
|
+
.\scripts\install.ps1 # Windows PowerShell — same idea
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
The build is driven by the `prepack` lifecycle hook (`scripts/build.mjs`),
|
|
334
|
+
which wipes `dist/` before recompiling. That keeps stale artifacts (e.g.
|
|
335
|
+
leftover from a branch switch) out of the tarball whether you ran
|
|
336
|
+
`npm pack`, `npm publish`, or `npm run package`.
|
|
337
|
+
|
|
338
|
+
What ships is controlled by the `files` field in `package.json` (currently
|
|
339
|
+
`bin/`, `dist/`, `README.md`, `LICENSE`). Source TypeScript and devDeps are
|
|
340
|
+
deliberately excluded.
|
|
341
|
+
|
|
342
|
+
### Publishing to npm
|
|
343
|
+
|
|
344
|
+
The package is configured to publish as `@este.systems/dsc` with public
|
|
345
|
+
access. To release:
|
|
346
|
+
|
|
347
|
+
```sh
|
|
348
|
+
# 1. Bump the version (semver). For a pre-1.0 patch:
|
|
349
|
+
npm version patch # → 0.1.1, also creates a git tag
|
|
350
|
+
|
|
351
|
+
# 2. Make sure you're logged in to npm:
|
|
352
|
+
npm whoami # should print your username
|
|
353
|
+
npm login # if not
|
|
354
|
+
|
|
355
|
+
# 3. (Optional) preview the tarball:
|
|
356
|
+
npm pack --dry-run
|
|
357
|
+
|
|
358
|
+
# 4. Publish:
|
|
359
|
+
npm publish # respects publishConfig.access=public
|
|
360
|
+
|
|
361
|
+
# 5. Push the version-bump commit and tag:
|
|
362
|
+
git push --follow-tags
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
Notes:
|
|
366
|
+
|
|
367
|
+
- The package name is **scoped** (`@este.systems/dsc`), so `npm publish`
|
|
368
|
+
defaults to private. `publishConfig.access = "public"` in `package.json`
|
|
369
|
+
overrides that. Don't drop it.
|
|
370
|
+
- npm now nudges hard for **2FA**. Enable with
|
|
371
|
+
`npm profile enable-2fa auth-and-writes`. You'll be asked for an OTP on
|
|
372
|
+
every publish.
|
|
373
|
+
- Once published, anyone can install with
|
|
374
|
+
`npm install -g @este.systems/dsc`. The CLI binary is still just `dsc`.
|
|
375
|
+
- After publish, the `pkg/*.tgz` produced locally is identical to what
|
|
376
|
+
you uploaded — useful for offline installs (`scripts/install.sh`).
|
|
377
|
+
|
|
378
|
+
## Development
|
|
379
|
+
|
|
380
|
+
```sh
|
|
381
|
+
npm run dev # tsx src/index.ts
|
|
382
|
+
npm run typecheck # tsc --noEmit
|
|
383
|
+
npm run build # compiles to dist/
|
|
384
|
+
npm run package # build + npm pack into pkg/
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
Source layout:
|
|
388
|
+
|
|
389
|
+
```
|
|
390
|
+
src/
|
|
391
|
+
index.ts # REPL, slash commands, signal handling
|
|
392
|
+
agent.ts # tool-call loop, status formatting, repair logic
|
|
393
|
+
api.ts # DeepSeek client, retry/abort, prompt cache rates
|
|
394
|
+
tools.ts # tool schemas + executors (read/write/edit/bash/grep/glob/web_*)
|
|
395
|
+
approval.ts # confirmWrite/Edit/Bash/Fetch
|
|
396
|
+
audit.ts # JSONL audit logger
|
|
397
|
+
search.ts # Brave / Tavily / DDG dispatch
|
|
398
|
+
compact.ts # /compact summarization routine
|
|
399
|
+
history.ts # session save/load/list/migrate-legacy
|
|
400
|
+
repl_history.ts # ~/.local/state/dsc/history reader/writer
|
|
401
|
+
markdown.ts # streaming markdown→ANSI renderer (incl. tables, HR, LaTeX→Unicode)
|
|
402
|
+
ui.ts # Spinner with stall detection; one-line StatusBar
|
|
403
|
+
prompt.ts # SYSTEM_PROMPT + buildSystemPrompt
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
The `ink-port` branch is a parked experiment — a React-based ink TUI
|
|
407
|
+
shell. `main` deliberately stays a plain REPL so output remains
|
|
408
|
+
selectable.
|
package/bin/dsc.mjs
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { spawn } from "node:child_process";
|
|
3
|
+
import { existsSync } from "node:fs";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { dirname, join } from "node:path";
|
|
6
|
+
|
|
7
|
+
// Resolve the package root from this file's location. With `npm link` the
|
|
8
|
+
// global symlink resolves through to the real repo, so this points at the
|
|
9
|
+
// editable source — no rebuild needed.
|
|
10
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
11
|
+
const pkgRoot = dirname(here);
|
|
12
|
+
const tsxBin = join(pkgRoot, "node_modules", ".bin", "tsx");
|
|
13
|
+
const srcEntry = join(pkgRoot, "src", "index.ts");
|
|
14
|
+
const distEntry = join(pkgRoot, "dist", "index.js");
|
|
15
|
+
|
|
16
|
+
if (existsSync(tsxBin) && existsSync(srcEntry)) {
|
|
17
|
+
// Dev: run TS sources directly. Live changes pick up on next launch.
|
|
18
|
+
const child = spawn(tsxBin, [srcEntry, ...process.argv.slice(2)], {
|
|
19
|
+
stdio: "inherit",
|
|
20
|
+
});
|
|
21
|
+
child.on("exit", (code, signal) => {
|
|
22
|
+
if (signal) process.kill(process.pid, signal);
|
|
23
|
+
else process.exit(code ?? 0);
|
|
24
|
+
});
|
|
25
|
+
} else {
|
|
26
|
+
// Fallback: production-style install without devDeps. Use the compiled
|
|
27
|
+
// output instead. Run `npm run build` to refresh `dist/`.
|
|
28
|
+
await import(distEntry);
|
|
29
|
+
}
|