codeep 3.1.0 → 3.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 +75 -74
- package/dist/config/providers.js +10 -5
- package/dist/renderer/agentExecution.js +9 -3
- package/dist/utils/telegramNotify.d.ts +30 -4
- package/dist/utils/telegramNotify.js +89 -20
- package/dist/utils/tokenTracker.js +11 -2
- package/dist/utils/update.js +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -20,9 +20,18 @@
|
|
|
20
20
|
<a href="https://www.npmjs.com/package/codeep"><img src="https://img.shields.io/npm/v/codeep.svg" alt="npm version"></a>
|
|
21
21
|
<a href="https://www.npmjs.com/package/codeep"><img src="https://img.shields.io/npm/dm/codeep.svg" alt="npm downloads"></a>
|
|
22
22
|
<a href="https://github.com/VladoIvankovic/Codeep/blob/main/LICENSE"><img src="https://img.shields.io/npm/l/codeep.svg" alt="license"></a>
|
|
23
|
-
<a href="https://github.com/VladoIvankovic/Codeep"><img src="https://img.shields.io/github/stars/VladoIvankovic/Codeep?style=social" alt="GitHub stars"></a>
|
|
24
23
|
</p>
|
|
25
24
|
|
|
25
|
+
## Contents
|
|
26
|
+
|
|
27
|
+
- [Install](#installation) · [Quick start](#quick-start) · [Upgrading](#upgrading)
|
|
28
|
+
- [One agent, every surface](#one-agent-every-surface) · [Features](#features) · [Dashboard](#codeep-dashboard)
|
|
29
|
+
- [Commands](#commands) · [Keyboard shortcuts](#keyboard-shortcuts) · [Configuration](#configuration)
|
|
30
|
+
- [Zed (ACP)](#zed-editor-integration-acp) · [VS Code](#vs-code-extension) · [Contributing](#contributing)
|
|
31
|
+
|
|
32
|
+
Full documentation: **[codeep.dev/docs](https://codeep.dev/docs)**
|
|
33
|
+
|
|
34
|
+
|
|
26
35
|
## One agent, every surface
|
|
27
36
|
|
|
28
37
|
Codeep keeps the same project context, provider configuration, sessions, and
|
|
@@ -33,6 +42,71 @@ agent workflow wherever you work:
|
|
|
33
42
|
- **[VS Code extension](https://github.com/VladoIvankovic/Codeep-vscode)** — the same agent over ACP, directly inside the editor. Also available on the [VS Code Marketplace](https://marketplace.visualstudio.com/items?itemName=VladoIvankovic.codeep).
|
|
34
43
|
- **[Web dashboard](https://codeep.dev/dashboard)** — synced sessions, usage, costs, tasks, and project activity.
|
|
35
44
|
|
|
45
|
+
## Installation
|
|
46
|
+
|
|
47
|
+
### Option 1: npm (Recommended)
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
npm install -g codeep
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
**Update:**
|
|
54
|
+
```bash
|
|
55
|
+
npm update -g codeep
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Option 2: curl
|
|
59
|
+
|
|
60
|
+
For a machine without Node. The script is served from codeep.dev and fetched from
|
|
61
|
+
this repository's newest release, so the command does not change if the repository
|
|
62
|
+
moves.
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
curl -fsSL https://codeep.dev/install.sh | bash
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
**Custom installation directory:**
|
|
69
|
+
```bash
|
|
70
|
+
curl -fsSL https://codeep.dev/install.sh | INSTALL_DIR=~/.local/bin bash
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
**Specific version:**
|
|
74
|
+
```bash
|
|
75
|
+
curl -fsSL https://codeep.dev/install.sh | VERSION=3.1.1 bash
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Option 3: Manual Binary
|
|
79
|
+
|
|
80
|
+
Download the latest binary for your platform from [GitHub Releases](https://github.com/VladoIvankovic/Codeep/releases):
|
|
81
|
+
|
|
82
|
+
| Platform | Binary |
|
|
83
|
+
|----------|--------|
|
|
84
|
+
| macOS Apple Silicon (M1/M2/M3/M4) | `codeep-macos-arm64` |
|
|
85
|
+
| macOS Intel | `codeep-macos-x64` |
|
|
86
|
+
| Linux x86_64 | `codeep-linux-x64` |
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
# Example for macOS Apple Silicon:
|
|
90
|
+
curl -fsSL https://github.com/VladoIvankovic/Codeep/releases/latest/download/codeep-macos-arm64 -o codeep
|
|
91
|
+
chmod +x codeep
|
|
92
|
+
sudo mv codeep /usr/local/bin/
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Quick Start
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
# Navigate to your project directory
|
|
99
|
+
cd /path/to/your/project
|
|
100
|
+
|
|
101
|
+
# Start Codeep
|
|
102
|
+
codeep
|
|
103
|
+
|
|
104
|
+
# On first run, enter your API key
|
|
105
|
+
# Get one at: https://z.ai/subscribe?ic=NXYNXZOV14
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
After installation, `codeep` is available globally in your terminal. Simply run it from any project directory to start coding with AI assistance.
|
|
109
|
+
|
|
36
110
|
## Upgrading
|
|
37
111
|
|
|
38
112
|
Running `npm install -g codeep@latest` picks up the new version — config,
|
|
@@ -966,79 +1040,6 @@ Create, view, and complete tasks directly from the CLI — or manage them on the
|
|
|
966
1040
|
|
|
967
1041
|
Tasks are loaded into the agent context so the AI sees them automatically on the next message.
|
|
968
1042
|
|
|
969
|
-
## Installation
|
|
970
|
-
|
|
971
|
-
### Option 1: curl (Quickest)
|
|
972
|
-
|
|
973
|
-
```bash
|
|
974
|
-
curl -fsSL https://raw.githubusercontent.com/VladoIvankovic/Codeep/main/install.sh | bash
|
|
975
|
-
```
|
|
976
|
-
|
|
977
|
-
**Custom installation directory:**
|
|
978
|
-
```bash
|
|
979
|
-
curl -fsSL https://raw.githubusercontent.com/VladoIvankovic/Codeep/main/install.sh | INSTALL_DIR=~/.local/bin bash
|
|
980
|
-
```
|
|
981
|
-
|
|
982
|
-
**Specific version:**
|
|
983
|
-
```bash
|
|
984
|
-
curl -fsSL https://raw.githubusercontent.com/VladoIvankovic/Codeep/main/install.sh | VERSION=1.0.0 bash
|
|
985
|
-
```
|
|
986
|
-
|
|
987
|
-
### Option 2: Homebrew (macOS/Linux)
|
|
988
|
-
|
|
989
|
-
```bash
|
|
990
|
-
brew tap VladoIvankovic/codeep
|
|
991
|
-
brew install codeep
|
|
992
|
-
```
|
|
993
|
-
|
|
994
|
-
**Update:**
|
|
995
|
-
```bash
|
|
996
|
-
brew upgrade codeep
|
|
997
|
-
```
|
|
998
|
-
|
|
999
|
-
### Option 3: npm
|
|
1000
|
-
|
|
1001
|
-
```bash
|
|
1002
|
-
npm install -g codeep
|
|
1003
|
-
```
|
|
1004
|
-
|
|
1005
|
-
**Update:**
|
|
1006
|
-
```bash
|
|
1007
|
-
npm update -g codeep
|
|
1008
|
-
```
|
|
1009
|
-
|
|
1010
|
-
### Option 4: Manual Binary
|
|
1011
|
-
|
|
1012
|
-
Download the latest binary for your platform from [GitHub Releases](https://github.com/VladoIvankovic/Codeep/releases):
|
|
1013
|
-
|
|
1014
|
-
| Platform | Binary |
|
|
1015
|
-
|----------|--------|
|
|
1016
|
-
| macOS Apple Silicon (M1/M2/M3/M4) | `codeep-macos-arm64` |
|
|
1017
|
-
| macOS Intel | `codeep-macos-x64` |
|
|
1018
|
-
| Linux x86_64 | `codeep-linux-x64` |
|
|
1019
|
-
|
|
1020
|
-
```bash
|
|
1021
|
-
# Example for macOS Apple Silicon:
|
|
1022
|
-
curl -fsSL https://github.com/VladoIvankovic/Codeep/releases/latest/download/codeep-macos-arm64 -o codeep
|
|
1023
|
-
chmod +x codeep
|
|
1024
|
-
sudo mv codeep /usr/local/bin/
|
|
1025
|
-
```
|
|
1026
|
-
|
|
1027
|
-
## Quick Start
|
|
1028
|
-
|
|
1029
|
-
```bash
|
|
1030
|
-
# Navigate to your project directory
|
|
1031
|
-
cd /path/to/your/project
|
|
1032
|
-
|
|
1033
|
-
# Start Codeep
|
|
1034
|
-
codeep
|
|
1035
|
-
|
|
1036
|
-
# On first run, enter your API key
|
|
1037
|
-
# Get one at: https://z.ai/subscribe?ic=NXYNXZOV14
|
|
1038
|
-
```
|
|
1039
|
-
|
|
1040
|
-
After installation, `codeep` is available globally in your terminal. Simply run it from any project directory to start coding with AI assistance.
|
|
1041
|
-
|
|
1042
1043
|
## Commands
|
|
1043
1044
|
|
|
1044
1045
|
### General
|
package/dist/config/providers.js
CHANGED
|
@@ -453,6 +453,7 @@ export const PROVIDERS = {
|
|
|
453
453
|
},
|
|
454
454
|
},
|
|
455
455
|
models: [
|
|
456
|
+
{ id: 'gpt-6-astra', name: 'GPT-6 Astra', description: 'Frontier GPT — 1M context, 128K output. Rolling out by organization, so a key may not have it yet. 2x the price of 5.6 Sol' },
|
|
456
457
|
{ id: 'gpt-5.6-sol', name: 'GPT-5.6 Sol', description: 'Most capable GPT — best for coding & agentic work' },
|
|
457
458
|
{ id: 'gpt-5.6-terra', name: 'GPT-5.6 Terra', description: 'Balanced — GPT-5.5 quality at about half the price' },
|
|
458
459
|
{ id: 'gpt-5.6-luna', name: 'GPT-5.6 Luna', description: 'Fast and cheap — high-volume workloads' },
|
|
@@ -501,8 +502,9 @@ export const PROVIDERS = {
|
|
|
501
502
|
},
|
|
502
503
|
models: [
|
|
503
504
|
{ id: 'gemini-3.1-pro-preview', name: 'Gemini 3.1 Pro', description: 'Most capable Gemini model' },
|
|
504
|
-
{ id: 'gemini-3.
|
|
505
|
-
{ id: 'gemini-3.
|
|
505
|
+
{ id: 'gemini-3.8-flash', name: 'Gemini 3.8 Flash', description: 'Latest production Flash model — adjustable thinking, 64K output' },
|
|
506
|
+
{ id: 'gemini-3.7-flash', name: 'Gemini 3.7 Flash', description: 'Previous production Flash model' },
|
|
507
|
+
{ id: 'gemini-3.6-flash', name: 'Gemini 3.6 Flash', description: 'Earlier production Flash model' },
|
|
506
508
|
{ id: 'gemini-3.5-flash', name: 'Gemini 3.5 Flash', description: 'Stable frontier Flash model for coding and long agentic tasks' },
|
|
507
509
|
{ id: 'gemini-3.5-flash-lite', name: 'Gemini 3.5 Flash-Lite', description: 'Latest low-latency, low-cost workhorse' },
|
|
508
510
|
],
|
|
@@ -783,7 +785,7 @@ export function providerNoStreamWithTools(providerId) {
|
|
|
783
785
|
const SAMPLING_PARAMS_REJECTED = [
|
|
784
786
|
'claude-fable-5', 'claude-opus-5', 'claude-opus-4-8', 'claude-opus-4-7', 'claude-sonnet-5',
|
|
785
787
|
'kimi-k3', 'kimi-k2.7-code', 'kimi-for-coding', 'k3',
|
|
786
|
-
'gemini-3.7-flash',
|
|
788
|
+
'gemini-3.7-flash', 'gemini-3.8-flash',
|
|
787
789
|
];
|
|
788
790
|
export function modelRejectsSamplingParams(model) {
|
|
789
791
|
// Canonicalize both sides. OpenRouter routes these as `google/gemini-3.7-flash`
|
|
@@ -837,8 +839,11 @@ export function modelSupportsReasoningEffort(providerId, model) {
|
|
|
837
839
|
return false;
|
|
838
840
|
return /^claude-(opus-5|opus-4-([5-9]|\d\d)|sonnet-(4-6|5)|fable-5)/.test(id);
|
|
839
841
|
case 'openai':
|
|
840
|
-
// GPT-5.x are reasoning models — reasoning_effort across
|
|
841
|
-
|
|
842
|
+
// GPT-5.x and GPT-6.x are reasoning models — reasoning_effort across both
|
|
843
|
+
// families (incl. mini). Written as two prefixes rather than `gpt-`
|
|
844
|
+
// because GPT-4 and earlier are not reasoning models and must not be
|
|
845
|
+
// offered the parameter.
|
|
846
|
+
return id.startsWith('gpt-5') || id.startsWith('gpt-6');
|
|
842
847
|
case 'google':
|
|
843
848
|
// Gemini 3.x thinking_level via the OpenAI-compat reasoning_effort mapping.
|
|
844
849
|
return id.startsWith('gemini-3');
|
|
@@ -9,7 +9,7 @@ import { chat } from '../api/index.js';
|
|
|
9
9
|
import { runAgent } from '../utils/agent.js';
|
|
10
10
|
import { TelegramApproval, outcomeForAnswer, describePermissionOutcome } from '../utils/telegramApproval.js';
|
|
11
11
|
import { loadTelegramCredentials } from '../utils/telegramCredentials.js';
|
|
12
|
-
import {
|
|
12
|
+
import { composeRunMessages, sendTelegramNotice, shouldNotify } from '../utils/telegramNotify.js';
|
|
13
13
|
import { takeRunFromPhone } from '../utils/telegramInbox.js';
|
|
14
14
|
import { isFlatFeeProvider } from '../config/providers.js';
|
|
15
15
|
import { raceApproval } from '../utils/approvalRace.js';
|
|
@@ -470,13 +470,19 @@ export async function executeAgentTask(task, dryRun, ctx) {
|
|
|
470
470
|
// and withholding it leaves "Started —" as the last word.
|
|
471
471
|
if (fromPhone || shouldNotify(elapsedMs, true)) {
|
|
472
472
|
const payPerUse = costBreakdown.filter(entry => !isFlatFeeProvider(entry.provider));
|
|
473
|
-
|
|
473
|
+
// Usually one message. An answer past Telegram's limit continues into
|
|
474
|
+
// further ones rather than being cut at the first — awaited in turn so
|
|
475
|
+
// they arrive in the order they were written.
|
|
476
|
+
const messages = composeRunMessages({
|
|
474
477
|
task: runLabel,
|
|
475
478
|
elapsedMs,
|
|
476
479
|
answer: fromPhone ? result.finalResponse : undefined,
|
|
477
480
|
tokens: costBreakdown.reduce((sum, e) => sum + e.promptTokens + e.completionTokens, 0),
|
|
478
481
|
costUsd: payPerUse.reduce((sum, e) => sum + e.estimatedCost, 0),
|
|
479
|
-
})
|
|
482
|
+
});
|
|
483
|
+
for (const message of messages) {
|
|
484
|
+
await sendTelegramNotice(noticeCredentials, message).catch(() => false);
|
|
485
|
+
}
|
|
480
486
|
}
|
|
481
487
|
}
|
|
482
488
|
const sharedFields = {
|
|
@@ -15,10 +15,31 @@ export declare function formatDuration(ms: number): string;
|
|
|
15
15
|
/**
|
|
16
16
|
* Telegram refuses a message over 4096 characters outright.
|
|
17
17
|
*
|
|
18
|
-
* Kept well under
|
|
19
|
-
* that fills a phone screen twice over is not read on a phone anyway.
|
|
18
|
+
* Kept well under, because the summary head shares the first message.
|
|
20
19
|
*/
|
|
21
20
|
export declare const MAX_ANSWER_LENGTH = 3000;
|
|
21
|
+
/**
|
|
22
|
+
* How many messages one answer may become.
|
|
23
|
+
*
|
|
24
|
+
* A long answer is worth several; an enormous one is not worth a phone buzzing
|
|
25
|
+
* eleven times, and past a point nobody is reading it there anyway. Three is
|
|
26
|
+
* enough for an explanation and short of a flood.
|
|
27
|
+
*/
|
|
28
|
+
export declare const MAX_ANSWER_PARTS = 3;
|
|
29
|
+
/**
|
|
30
|
+
* An answer as the messages to send, in order.
|
|
31
|
+
*
|
|
32
|
+
* Cutting at the limit was honest — it said it had cut — but lossy in the place
|
|
33
|
+
* it hurts: an answer that lists files or walks through a change passes 3000
|
|
34
|
+
* characters easily, and the conclusion is at the end. So it is split instead,
|
|
35
|
+
* on a paragraph or line boundary where there is one nearby, and only what
|
|
36
|
+
* exceeds three messages is cut.
|
|
37
|
+
*
|
|
38
|
+
* Mirrors the Mac app's `TelegramAnswerText.partsForPhone` deliberately: two
|
|
39
|
+
* implementations of "what does the phone get" that can disagree is worse than
|
|
40
|
+
* either.
|
|
41
|
+
*/
|
|
42
|
+
export declare function splitAnswer(text: string): string[];
|
|
22
43
|
/**
|
|
23
44
|
* Markdown out, plain words in.
|
|
24
45
|
*
|
|
@@ -57,7 +78,12 @@ export interface RunSummary {
|
|
|
57
78
|
answer?: string;
|
|
58
79
|
}
|
|
59
80
|
/**
|
|
60
|
-
* The notification
|
|
81
|
+
* The notification, as the messages to send in order.
|
|
82
|
+
*
|
|
83
|
+
* One message when the answer fits, which is the common case. A longer answer
|
|
84
|
+
* continues into further messages rather than being cut at the first limit —
|
|
85
|
+
* see `splitAnswer`. The head shares the first message, so the reply is not a
|
|
86
|
+
* bare wall of text with no idea which run it belongs to.
|
|
61
87
|
*
|
|
62
88
|
* Carries the agent's answer only when the run was started from the phone. A
|
|
63
89
|
* finished run can end with anything in it — a file it read, a command it ran,
|
|
@@ -65,7 +91,7 @@ export interface RunSummary {
|
|
|
65
91
|
* servers, so it travels only where it was actually asked for. Start a run at
|
|
66
92
|
* the terminal and this says there is a result to come back to, and no more.
|
|
67
93
|
*/
|
|
68
|
-
export declare function
|
|
94
|
+
export declare function composeRunMessages(summary: RunSummary): string[];
|
|
69
95
|
/**
|
|
70
96
|
* Send it, and say nothing if it fails.
|
|
71
97
|
*
|
|
@@ -34,10 +34,74 @@ export function formatDuration(ms) {
|
|
|
34
34
|
/**
|
|
35
35
|
* Telegram refuses a message over 4096 characters outright.
|
|
36
36
|
*
|
|
37
|
-
* Kept well under
|
|
38
|
-
* that fills a phone screen twice over is not read on a phone anyway.
|
|
37
|
+
* Kept well under, because the summary head shares the first message.
|
|
39
38
|
*/
|
|
40
39
|
export const MAX_ANSWER_LENGTH = 3000;
|
|
40
|
+
/**
|
|
41
|
+
* How many messages one answer may become.
|
|
42
|
+
*
|
|
43
|
+
* A long answer is worth several; an enormous one is not worth a phone buzzing
|
|
44
|
+
* eleven times, and past a point nobody is reading it there anyway. Three is
|
|
45
|
+
* enough for an explanation and short of a flood.
|
|
46
|
+
*/
|
|
47
|
+
export const MAX_ANSWER_PARTS = 3;
|
|
48
|
+
/**
|
|
49
|
+
* An answer as the messages to send, in order.
|
|
50
|
+
*
|
|
51
|
+
* Cutting at the limit was honest — it said it had cut — but lossy in the place
|
|
52
|
+
* it hurts: an answer that lists files or walks through a change passes 3000
|
|
53
|
+
* characters easily, and the conclusion is at the end. So it is split instead,
|
|
54
|
+
* on a paragraph or line boundary where there is one nearby, and only what
|
|
55
|
+
* exceeds three messages is cut.
|
|
56
|
+
*
|
|
57
|
+
* Mirrors the Mac app's `TelegramAnswerText.partsForPhone` deliberately: two
|
|
58
|
+
* implementations of "what does the phone get" that can disagree is worse than
|
|
59
|
+
* either.
|
|
60
|
+
*/
|
|
61
|
+
export function splitAnswer(text) {
|
|
62
|
+
const plain = text.trim();
|
|
63
|
+
if (!plain)
|
|
64
|
+
return [];
|
|
65
|
+
const parts = [];
|
|
66
|
+
let rest = plain;
|
|
67
|
+
while (rest.length > 0 && parts.length < MAX_ANSWER_PARTS) {
|
|
68
|
+
if (rest.length <= MAX_ANSWER_LENGTH) {
|
|
69
|
+
parts.push(rest);
|
|
70
|
+
rest = '';
|
|
71
|
+
break;
|
|
72
|
+
}
|
|
73
|
+
const cut = breakPoint(rest);
|
|
74
|
+
parts.push(rest.slice(0, cut).trim());
|
|
75
|
+
rest = rest.slice(cut).replace(/^[\n ]+/, '');
|
|
76
|
+
}
|
|
77
|
+
// Say it was cut rather than ending mid-sentence and looking finished.
|
|
78
|
+
if (rest.length > 0 && parts.length > 0) {
|
|
79
|
+
parts[parts.length - 1] += '\n\n[…cut — the full answer is in the terminal]';
|
|
80
|
+
}
|
|
81
|
+
return parts;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Where to end a part: the last paragraph break in the final third of the
|
|
85
|
+
* allowance, else the last line break, else the last space.
|
|
86
|
+
*
|
|
87
|
+
* Splitting mid-word reads as damage; splitting mid-paragraph reads as a
|
|
88
|
+
* continuation. Only the tail is searched so a single long paragraph does not
|
|
89
|
+
* send a 200-character message and push the rest into the next one.
|
|
90
|
+
*/
|
|
91
|
+
function breakPoint(text) {
|
|
92
|
+
const earliest = Math.floor((MAX_ANSWER_LENGTH * 2) / 3);
|
|
93
|
+
const window = text.slice(earliest, MAX_ANSWER_LENGTH);
|
|
94
|
+
const paragraph = window.lastIndexOf('\n\n');
|
|
95
|
+
if (paragraph >= 0)
|
|
96
|
+
return earliest + paragraph;
|
|
97
|
+
const line = window.lastIndexOf('\n');
|
|
98
|
+
if (line >= 0)
|
|
99
|
+
return earliest + line;
|
|
100
|
+
const space = window.lastIndexOf(' ');
|
|
101
|
+
if (space >= 0)
|
|
102
|
+
return earliest + space;
|
|
103
|
+
return MAX_ANSWER_LENGTH;
|
|
104
|
+
}
|
|
41
105
|
/**
|
|
42
106
|
* Markdown out, plain words in.
|
|
43
107
|
*
|
|
@@ -69,16 +133,8 @@ export function stripMarkdown(text) {
|
|
|
69
133
|
.replace(/`([^`\n]+)`/g, '$1')
|
|
70
134
|
.trim();
|
|
71
135
|
}
|
|
72
|
-
/**
|
|
73
|
-
|
|
74
|
-
*
|
|
75
|
-
* Carries the agent's answer only when the run was started from the phone. A
|
|
76
|
-
* finished run can end with anything in it — a file it read, a command it ran,
|
|
77
|
-
* a secret inside an error — and this goes to a chat that syncs to Telegram's
|
|
78
|
-
* servers, so it travels only where it was actually asked for. Start a run at
|
|
79
|
-
* the terminal and this says there is a result to come back to, and no more.
|
|
80
|
-
*/
|
|
81
|
-
export function composeRunSummary(summary) {
|
|
136
|
+
/** The head: what ran, how long it took, what it cost. Never the answer. */
|
|
137
|
+
function composeHead(summary) {
|
|
82
138
|
const head = summary.failure ? '⚠️ Codeep stopped' : '✅ Codeep finished';
|
|
83
139
|
const lines = [`${head} — ${summary.task}`, `took ${formatDuration(summary.elapsedMs)}`];
|
|
84
140
|
if (summary.failure)
|
|
@@ -92,16 +148,29 @@ export function composeRunSummary(summary) {
|
|
|
92
148
|
}
|
|
93
149
|
if (cost.length > 0)
|
|
94
150
|
lines.push(cost.join(' · '));
|
|
95
|
-
const answer = summary.answer ? stripMarkdown(summary.answer) : undefined;
|
|
96
|
-
if (answer) {
|
|
97
|
-
lines.push('');
|
|
98
|
-
lines.push(answer.length > MAX_ANSWER_LENGTH
|
|
99
|
-
// Say it was cut rather than ending mid-sentence and looking finished.
|
|
100
|
-
? `${answer.slice(0, MAX_ANSWER_LENGTH)}\n\n[…cut — the full answer is in the terminal]`
|
|
101
|
-
: answer);
|
|
102
|
-
}
|
|
103
151
|
return lines.join('\n');
|
|
104
152
|
}
|
|
153
|
+
/**
|
|
154
|
+
* The notification, as the messages to send in order.
|
|
155
|
+
*
|
|
156
|
+
* One message when the answer fits, which is the common case. A longer answer
|
|
157
|
+
* continues into further messages rather than being cut at the first limit —
|
|
158
|
+
* see `splitAnswer`. The head shares the first message, so the reply is not a
|
|
159
|
+
* bare wall of text with no idea which run it belongs to.
|
|
160
|
+
*
|
|
161
|
+
* Carries the agent's answer only when the run was started from the phone. A
|
|
162
|
+
* finished run can end with anything in it — a file it read, a command it ran,
|
|
163
|
+
* a secret inside an error — and this goes to a chat that syncs to Telegram's
|
|
164
|
+
* servers, so it travels only where it was actually asked for. Start a run at
|
|
165
|
+
* the terminal and this says there is a result to come back to, and no more.
|
|
166
|
+
*/
|
|
167
|
+
export function composeRunMessages(summary) {
|
|
168
|
+
const head = composeHead(summary);
|
|
169
|
+
const parts = summary.answer ? splitAnswer(stripMarkdown(summary.answer)) : [];
|
|
170
|
+
if (parts.length === 0)
|
|
171
|
+
return [head];
|
|
172
|
+
return [`${head}\n\n${parts[0]}`, ...parts.slice(1)];
|
|
173
|
+
}
|
|
105
174
|
/**
|
|
106
175
|
* Send it, and say nothing if it fails.
|
|
107
176
|
*
|
|
@@ -15,6 +15,7 @@ const MODEL_CONTEXT_WINDOWS = {
|
|
|
15
15
|
'glm-5.1': 200_000,
|
|
16
16
|
'glm-5-turbo': 202_752,
|
|
17
17
|
// OpenAI
|
|
18
|
+
'gpt-6-astra': 1_050_000,
|
|
18
19
|
'gpt-5.6-sol': 1_050_000,
|
|
19
20
|
'gpt-5.6-terra': 1_050_000,
|
|
20
21
|
'gpt-5.6-luna': 1_050_000,
|
|
@@ -33,6 +34,7 @@ const MODEL_CONTEXT_WINDOWS = {
|
|
|
33
34
|
'deepseek-v4-flash': 1_000_000,
|
|
34
35
|
// Google
|
|
35
36
|
'gemini-3.1-pro-preview': 1_048_576,
|
|
37
|
+
'gemini-3.8-flash': 1_048_576,
|
|
36
38
|
'gemini-3.7-flash': 1_048_576,
|
|
37
39
|
'gemini-3.6-flash': 1_048_576,
|
|
38
40
|
'gemini-3.5-flash': 1_048_576,
|
|
@@ -91,6 +93,9 @@ const MODEL_PRICING = {
|
|
|
91
93
|
'glm-5.1': { inputPer1M: 1.40, outputPer1M: 4.40 },
|
|
92
94
|
'glm-5-turbo': { inputPer1M: 1.20, outputPer1M: 4.00 },
|
|
93
95
|
// OpenAI
|
|
96
|
+
// Twice the price of 5.6 Sol. Cached reads are $1.00/M — 0.1x input, which is
|
|
97
|
+
// what DEFAULT_CACHE_READ_RATE already applies, so no model row is needed.
|
|
98
|
+
'gpt-6-astra': { inputPer1M: 10.00, outputPer1M: 50.00 },
|
|
94
99
|
'gpt-5.6-sol': { inputPer1M: 5.00, outputPer1M: 30.00 },
|
|
95
100
|
'gpt-5.6-terra': { inputPer1M: 2.00, outputPer1M: 12.00 },
|
|
96
101
|
'gpt-5.6-luna': { inputPer1M: 0.20, outputPer1M: 1.20 },
|
|
@@ -114,9 +119,13 @@ const MODEL_PRICING = {
|
|
|
114
119
|
'deepseek-v4-pro': { inputPer1M: 0.435, outputPer1M: 0.87 },
|
|
115
120
|
'deepseek-v4-flash': { inputPer1M: 0.14, outputPer1M: 0.28 },
|
|
116
121
|
// Google
|
|
117
|
-
// Gemini 3.6/3.7 Flash carry PROMOTIONAL rates that run through 2026-12-31
|
|
118
|
-
// step up to 1.50/7.50 on 2027-01-01 — revisit
|
|
122
|
+
// Gemini 3.6/3.7/3.8 Flash carry PROMOTIONAL rates that run through 2026-12-31
|
|
123
|
+
// and are scheduled to step up to 1.50/7.50 on 2027-01-01 — revisit all three
|
|
124
|
+
// rows on that date. Deliberately NOT written ahead of time: Anthropic
|
|
125
|
+
// cancelled exactly such a scheduled rise for Sonnet 5, and the pre-entered
|
|
126
|
+
// figure over-reported every run by 50% until someone noticed by hand.
|
|
119
127
|
'gemini-3.1-pro-preview': { inputPer1M: 2.00, outputPer1M: 12.00 },
|
|
128
|
+
'gemini-3.8-flash': { inputPer1M: 0.75, outputPer1M: 3.75 },
|
|
120
129
|
'gemini-3.7-flash': { inputPer1M: 0.75, outputPer1M: 3.75 },
|
|
121
130
|
'gemini-3.6-flash': { inputPer1M: 0.75, outputPer1M: 3.75 },
|
|
122
131
|
'gemini-3.5-flash': { inputPer1M: 1.50, outputPer1M: 9.00 },
|
package/dist/utils/update.js
CHANGED
|
@@ -109,7 +109,7 @@ export function getUpdateInstructions() {
|
|
|
109
109
|
case 'npm':
|
|
110
110
|
return 'npm update -g codeep';
|
|
111
111
|
case 'binary':
|
|
112
|
-
return 'curl -fsSL https://
|
|
112
|
+
return 'curl -fsSL https://codeep.dev/install.sh | bash';
|
|
113
113
|
default:
|
|
114
114
|
return 'Visit: https://codeep.dev';
|
|
115
115
|
}
|
package/dist/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const VERSION = "3.
|
|
1
|
+
export declare const VERSION = "3.2.0";
|
package/dist/version.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
// AUTO-GENERATED by scripts/gen-version.js — do not edit by hand.
|
|
2
2
|
// Baked from package.json at build time so the bun-compiled binary reports
|
|
3
3
|
// the right version (it has no package.json on disk to read at runtime).
|
|
4
|
-
export const VERSION = '3.
|
|
4
|
+
export const VERSION = '3.2.0';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codeep",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.2.0",
|
|
4
4
|
"description": "AI-powered coding assistant built for the terminal. Multiple LLM providers, project-aware context, and a seamless development workflow.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|