@tractorscorch/clank 1.1.0 → 1.2.1
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/CHANGELOG.md +19 -0
- package/README.md +3 -3
- package/dist/index.js +300 -72
- package/dist/index.js.map +1 -1
- package/package.json +58 -58
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,25 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/).
|
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
9
|
+
## [1.2.1] — 2026-03-23
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
- **Gateway crash on restart** — stale Telegram messages queued while offline no longer flood the model. Messages older than 30s before startup are dropped.
|
|
13
|
+
- **Parallel model overload** — Telegram messages from the same chat are now processed sequentially (per-chat queue) instead of all at once.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## [1.2.0] — 2026-03-22
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
- **Two-tier context compaction** — critical for local model performance:
|
|
21
|
+
- Tier 1 (fast): system prompt budgeting, tool result dedup, message truncation, aggressive dropping
|
|
22
|
+
- Tier 2 (LLM-summarized): model generates conversation recap replacing oldest messages. Preserves meaning over long sessions.
|
|
23
|
+
- Token budgeting: reserves 25% for response, budgets system prompt separately from conversation
|
|
24
|
+
- **`clank update`** — update to latest npm version, preserves config/sessions/memory, restarts gateway
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
9
28
|
## [1.1.0] — 2026-03-22
|
|
10
29
|
|
|
11
30
|
### Security Hardening
|
package/README.md
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
<p align="center">
|
|
12
12
|
<a href="https://github.com/ItsTrag1c/Clank/releases/latest"><img src="https://img.shields.io/badge/version-1.1.0-blue.svg" alt="Version" /></a>
|
|
13
13
|
<a href="https://opensource.org/licenses/MIT"><img src="https://img.shields.io/badge/License-MIT-blue.svg" alt="License" /></a>
|
|
14
|
-
<a href="https://www.npmjs.com/package/clank"><img src="https://img.shields.io/npm/v/clank.svg" alt="npm" /></a>
|
|
14
|
+
<a href="https://www.npmjs.com/package/@tractorscorch/clank"><img src="https://img.shields.io/npm/v/@tractorscorch/clank.svg" alt="npm" /></a>
|
|
15
15
|
<a href="https://github.com/ItsTrag1c/Clank/stargazers"><img src="https://img.shields.io/github/stars/ItsTrag1c/Clank.svg" alt="Stars" /></a>
|
|
16
16
|
</p>
|
|
17
17
|
|
|
@@ -53,7 +53,7 @@ Clank is a personal AI gateway — **one daemon, many frontends**. It connects y
|
|
|
53
53
|
## Quick Start
|
|
54
54
|
|
|
55
55
|
```bash
|
|
56
|
-
npm install -g clank
|
|
56
|
+
npm install -g @tractorscorch/clank
|
|
57
57
|
clank setup
|
|
58
58
|
clank
|
|
59
59
|
```
|
|
@@ -157,7 +157,7 @@ See [SECURITY.md](SECURITY.md) for the full security model.
|
|
|
157
157
|
|--|--|
|
|
158
158
|
| **Website** | [clanksuite.dev](https://clanksuite.dev) |
|
|
159
159
|
| **GitHub** | [ItsTrag1c/Clank](https://github.com/ItsTrag1c/Clank) |
|
|
160
|
-
| **npm** | [npmjs.com/package/clank](https://www.npmjs.com/package/clank) |
|
|
160
|
+
| **npm** | [npmjs.com/package/@tractorscorch/clank](https://www.npmjs.com/package/@tractorscorch/clank) |
|
|
161
161
|
| **Twitter/X** | [@ClankSuite](https://x.com/ClankSuite) |
|
|
162
162
|
| **Reddit** | [u/ClankSuite](https://reddit.com/u/ClankSuite) |
|
|
163
163
|
| **Legacy** | [Clank-Legacy](https://github.com/ItsTrag1c/Clank-Legacy) (archived CLI v2.7.0 + Desktop v2.6.1) |
|
package/dist/index.js
CHANGED
|
@@ -28,10 +28,25 @@ var init_context_engine = __esm({
|
|
|
28
28
|
messages = [];
|
|
29
29
|
contextWindowSize;
|
|
30
30
|
isLocal;
|
|
31
|
+
systemPromptTokens = 0;
|
|
32
|
+
/** Provider for tier 2 LLM-summarized compaction */
|
|
33
|
+
provider = null;
|
|
34
|
+
modelId = "";
|
|
35
|
+
/** Cache of tool results by file path to detect duplicates */
|
|
36
|
+
toolResultHashes = /* @__PURE__ */ new Map();
|
|
31
37
|
constructor(opts) {
|
|
32
38
|
this.contextWindowSize = opts.contextWindow;
|
|
33
39
|
this.isLocal = opts.isLocal;
|
|
34
40
|
}
|
|
41
|
+
/** Set the provider for tier 2 compaction */
|
|
42
|
+
setProvider(provider, modelId) {
|
|
43
|
+
this.provider = provider;
|
|
44
|
+
this.modelId = modelId;
|
|
45
|
+
}
|
|
46
|
+
/** Set the system prompt size for token budgeting */
|
|
47
|
+
setSystemPromptSize(tokens) {
|
|
48
|
+
this.systemPromptTokens = tokens;
|
|
49
|
+
}
|
|
35
50
|
/** Get all messages */
|
|
36
51
|
getMessages() {
|
|
37
52
|
return this.messages;
|
|
@@ -63,38 +78,99 @@ var init_context_engine = __esm({
|
|
|
63
78
|
}
|
|
64
79
|
return Math.ceil(chars / 4);
|
|
65
80
|
}
|
|
66
|
-
/**
|
|
67
|
-
|
|
68
|
-
|
|
81
|
+
/** Calculate token budgets */
|
|
82
|
+
getBudget() {
|
|
83
|
+
const responseReserve = Math.floor(this.contextWindowSize * 0.25);
|
|
84
|
+
const available = this.contextWindowSize - responseReserve;
|
|
85
|
+
const systemPrompt = Math.min(this.systemPromptTokens, Math.floor(available * 0.3));
|
|
86
|
+
const conversation = available - systemPrompt;
|
|
87
|
+
return { systemPrompt, conversation, responseReserve };
|
|
88
|
+
}
|
|
89
|
+
/** Get context utilization as a percentage of the conversation budget */
|
|
69
90
|
utilizationPercent() {
|
|
70
|
-
|
|
91
|
+
const budget = this.getBudget();
|
|
92
|
+
return this.estimateTokens() / budget.conversation * 100;
|
|
71
93
|
}
|
|
72
94
|
/**
|
|
73
95
|
* Check if compaction is needed.
|
|
74
96
|
* Local models trigger earlier (60%) to leave room for the response.
|
|
75
|
-
* Cloud models can wait longer (80%)
|
|
97
|
+
* Cloud models can wait longer (80%).
|
|
76
98
|
*/
|
|
77
99
|
needsCompaction() {
|
|
78
100
|
const threshold = this.isLocal ? 60 : 80;
|
|
79
101
|
return this.utilizationPercent() >= threshold;
|
|
80
102
|
}
|
|
81
103
|
/**
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
* This is the key local-model optimization. We aggressively
|
|
85
|
-
* reduce context while preserving the most important information:
|
|
86
|
-
* - Recent messages (protected zone)
|
|
87
|
-
* - System-level context
|
|
88
|
-
* - Key decision points
|
|
104
|
+
* Run compaction — tier 1 first, tier 2 if still over budget.
|
|
105
|
+
* Returns the result with which tier was used.
|
|
89
106
|
*/
|
|
107
|
+
async compactSmart() {
|
|
108
|
+
const before = this.messages.length;
|
|
109
|
+
const tokensBefore = this.estimateTokens();
|
|
110
|
+
this.compactTier1();
|
|
111
|
+
if (this.utilizationPercent() < 70) {
|
|
112
|
+
return {
|
|
113
|
+
ok: true,
|
|
114
|
+
tier: 1,
|
|
115
|
+
messagesBefore: before,
|
|
116
|
+
messagesAfter: this.messages.length,
|
|
117
|
+
estimatedTokensBefore: tokensBefore,
|
|
118
|
+
estimatedTokensAfter: this.estimateTokens()
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
if (this.provider) {
|
|
122
|
+
await this.compactTier2();
|
|
123
|
+
} else {
|
|
124
|
+
this.compactTier1Aggressive();
|
|
125
|
+
}
|
|
126
|
+
return {
|
|
127
|
+
ok: true,
|
|
128
|
+
tier: this.provider ? 2 : 1,
|
|
129
|
+
messagesBefore: before,
|
|
130
|
+
messagesAfter: this.messages.length,
|
|
131
|
+
estimatedTokensBefore: tokensBefore,
|
|
132
|
+
estimatedTokensAfter: this.estimateTokens()
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
/** Synchronous compact (backward compat — uses tier 1 only) */
|
|
90
136
|
compact() {
|
|
91
137
|
const before = this.messages.length;
|
|
92
138
|
const tokensBefore = this.estimateTokens();
|
|
139
|
+
this.compactTier1();
|
|
140
|
+
if (this.utilizationPercent() >= 70) {
|
|
141
|
+
this.compactTier1Aggressive();
|
|
142
|
+
}
|
|
143
|
+
return {
|
|
144
|
+
ok: true,
|
|
145
|
+
tier: 1,
|
|
146
|
+
messagesBefore: before,
|
|
147
|
+
messagesAfter: this.messages.length,
|
|
148
|
+
estimatedTokensBefore: tokensBefore,
|
|
149
|
+
estimatedTokensAfter: this.estimateTokens()
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Tier 1: Fast mechanical compaction (no LLM calls).
|
|
154
|
+
*/
|
|
155
|
+
compactTier1() {
|
|
93
156
|
const protectedCount = 6;
|
|
157
|
+
if (this.messages.length <= protectedCount) return;
|
|
94
158
|
const protectedZone = this.messages.slice(-protectedCount);
|
|
95
159
|
const compactable = this.messages.slice(0, -protectedCount);
|
|
96
160
|
const compacted = [];
|
|
97
|
-
|
|
161
|
+
const seenToolResults = /* @__PURE__ */ new Map();
|
|
162
|
+
for (let i = compactable.length - 1; i >= 0; i--) {
|
|
163
|
+
const msg = compactable[i];
|
|
164
|
+
if (msg.role === "tool" && msg.tool_call_id) {
|
|
165
|
+
const content = typeof msg.content === "string" ? msg.content : "";
|
|
166
|
+
const key = msg.tool_call_id;
|
|
167
|
+
if (!seenToolResults.has(key)) {
|
|
168
|
+
seenToolResults.set(key, i);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
for (let i = 0; i < compactable.length; i++) {
|
|
173
|
+
const msg = compactable[i];
|
|
98
174
|
if (msg._compacted) {
|
|
99
175
|
compacted.push(msg);
|
|
100
176
|
continue;
|
|
@@ -122,29 +198,98 @@ var init_context_engine = __esm({
|
|
|
122
198
|
} else {
|
|
123
199
|
compacted.push(msg);
|
|
124
200
|
}
|
|
201
|
+
} else if (msg.role === "user") {
|
|
202
|
+
if (content.length > 2e3) {
|
|
203
|
+
compacted.push({
|
|
204
|
+
...msg,
|
|
205
|
+
content: content.slice(0, 800) + "\n... (truncated)",
|
|
206
|
+
_compacted: true
|
|
207
|
+
});
|
|
208
|
+
} else {
|
|
209
|
+
compacted.push(msg);
|
|
210
|
+
}
|
|
125
211
|
} else {
|
|
126
212
|
compacted.push(msg);
|
|
127
213
|
}
|
|
128
214
|
}
|
|
129
215
|
this.messages = [...compacted, ...protectedZone];
|
|
130
|
-
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Tier 1 aggressive: drop oldest messages when regular compaction isn't enough.
|
|
219
|
+
*/
|
|
220
|
+
compactTier1Aggressive() {
|
|
221
|
+
const protectedCount = 6;
|
|
222
|
+
const budget = this.getBudget();
|
|
223
|
+
while (this.estimateTokens() > budget.conversation * 0.7 && this.messages.length > protectedCount + 2) {
|
|
131
224
|
const dropIdx = this.messages.findIndex((m) => m.role !== "system");
|
|
132
225
|
if (dropIdx === -1 || dropIdx >= this.messages.length - protectedCount) break;
|
|
133
226
|
this.messages.splice(dropIdx, 1);
|
|
134
227
|
}
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Tier 2: LLM-summarized compaction.
|
|
231
|
+
*
|
|
232
|
+
* Takes the oldest messages (outside protected zone), sends them to
|
|
233
|
+
* the model with a summarization prompt, and replaces them with a
|
|
234
|
+
* single "conversation recap" message. This preserves meaning that
|
|
235
|
+
* mechanical truncation loses.
|
|
236
|
+
*/
|
|
237
|
+
async compactTier2() {
|
|
238
|
+
if (!this.provider) return;
|
|
239
|
+
const protectedCount = 6;
|
|
240
|
+
if (this.messages.length <= protectedCount + 2) return;
|
|
241
|
+
const cutoff = Math.max(2, this.messages.length - protectedCount - 2);
|
|
242
|
+
const toSummarize = this.messages.slice(0, cutoff);
|
|
243
|
+
const toKeep = this.messages.slice(cutoff);
|
|
244
|
+
const conversationText = toSummarize.map((m) => {
|
|
245
|
+
const content = typeof m.content === "string" ? m.content : JSON.stringify(m.content);
|
|
246
|
+
const truncated = content.length > 500 ? content.slice(0, 500) + "..." : content;
|
|
247
|
+
return `${m.role}: ${truncated}`;
|
|
248
|
+
}).join("\n\n");
|
|
249
|
+
const summaryPrompt = [
|
|
250
|
+
"Summarize this conversation concisely. Capture:",
|
|
251
|
+
"- Key decisions made",
|
|
252
|
+
"- Files modified and why",
|
|
253
|
+
"- Current task status",
|
|
254
|
+
"- Important context the assistant needs going forward",
|
|
255
|
+
"",
|
|
256
|
+
"Be brief (under 300 words). Use bullet points.",
|
|
257
|
+
"",
|
|
258
|
+
"Conversation:",
|
|
259
|
+
conversationText
|
|
260
|
+
].join("\n");
|
|
261
|
+
try {
|
|
262
|
+
let summary = "";
|
|
263
|
+
for await (const event of this.provider.stream(
|
|
264
|
+
[{ role: "user", content: summaryPrompt }],
|
|
265
|
+
"You are a conversation summarizer. Output only the summary, nothing else.",
|
|
266
|
+
[]
|
|
267
|
+
// no tools
|
|
268
|
+
)) {
|
|
269
|
+
if (event.type === "text") {
|
|
270
|
+
summary += event.content;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
if (summary.trim()) {
|
|
274
|
+
const recapMessage = {
|
|
275
|
+
role: "user",
|
|
276
|
+
content: `[Conversation recap \u2014 earlier messages were compacted]
|
|
277
|
+
|
|
278
|
+
${summary.trim()}`,
|
|
279
|
+
_compacted: true
|
|
280
|
+
};
|
|
281
|
+
this.messages = [recapMessage, ...toKeep];
|
|
282
|
+
}
|
|
283
|
+
} catch {
|
|
284
|
+
this.compactTier1Aggressive();
|
|
285
|
+
}
|
|
142
286
|
}
|
|
143
287
|
/** Clear all messages */
|
|
144
288
|
clear() {
|
|
145
289
|
this.messages = [];
|
|
290
|
+
this.toolResultHashes.clear();
|
|
146
291
|
}
|
|
147
|
-
/** Update the context window size
|
|
292
|
+
/** Update the context window size */
|
|
148
293
|
setContextWindow(size) {
|
|
149
294
|
this.contextWindowSize = size;
|
|
150
295
|
}
|
|
@@ -567,10 +712,12 @@ var init_agent = __esm({
|
|
|
567
712
|
contextWindow: opts.provider.provider.contextWindow(),
|
|
568
713
|
isLocal: opts.provider.isLocal
|
|
569
714
|
});
|
|
715
|
+
this.contextEngine.setProvider(opts.provider.provider, opts.identity.model.primary);
|
|
570
716
|
}
|
|
571
717
|
/** Set the system prompt */
|
|
572
718
|
setSystemPrompt(prompt) {
|
|
573
719
|
this.systemPrompt = prompt;
|
|
720
|
+
this.contextEngine.setSystemPromptSize(Math.ceil(prompt.length / 4));
|
|
574
721
|
}
|
|
575
722
|
/** Load or create a session */
|
|
576
723
|
async loadSession(normalizedKey, channel) {
|
|
@@ -621,7 +768,15 @@ var init_agent = __esm({
|
|
|
621
768
|
iterationCount++;
|
|
622
769
|
if (this.contextEngine.needsCompaction()) {
|
|
623
770
|
this.emit("context-compacting");
|
|
624
|
-
this.contextEngine.
|
|
771
|
+
const compactResult = await this.contextEngine.compactSmart();
|
|
772
|
+
if (compactResult.tier === 2) {
|
|
773
|
+
this.emit("usage", {
|
|
774
|
+
promptTokens: 0,
|
|
775
|
+
outputTokens: 0,
|
|
776
|
+
iterationCount,
|
|
777
|
+
contextPercent: Math.round(this.contextEngine.utilizationPercent())
|
|
778
|
+
});
|
|
779
|
+
}
|
|
625
780
|
}
|
|
626
781
|
const toolDefs = this.toolRegistry.getDefinitions({
|
|
627
782
|
tier: this.identity.toolTier,
|
|
@@ -4577,11 +4732,17 @@ var init_telegram = __esm({
|
|
|
4577
4732
|
try {
|
|
4578
4733
|
this.bot = new Bot(telegramConfig.botToken);
|
|
4579
4734
|
const bot = this.bot;
|
|
4735
|
+
const startupTime = Math.floor(Date.now() / 1e3);
|
|
4736
|
+
const chatLocks = /* @__PURE__ */ new Map();
|
|
4580
4737
|
bot.on("message:text", async (ctx) => {
|
|
4581
4738
|
const msg = ctx.message;
|
|
4582
4739
|
const chatId = msg.chat.id;
|
|
4583
4740
|
const userId = msg.from?.id;
|
|
4584
4741
|
const isGroup = msg.chat.type === "group" || msg.chat.type === "supergroup";
|
|
4742
|
+
if (msg.date < startupTime - 30) {
|
|
4743
|
+
console.log(` Telegram: dropping stale message from ${userId} (${startupTime - msg.date}s old)`);
|
|
4744
|
+
return;
|
|
4745
|
+
}
|
|
4585
4746
|
if (telegramConfig.allowFrom && telegramConfig.allowFrom.length > 0) {
|
|
4586
4747
|
const username = msg.from?.username ? `@${msg.from.username}` : "";
|
|
4587
4748
|
const userIdStr = String(userId || "");
|
|
@@ -4605,27 +4766,33 @@ var init_telegram = __esm({
|
|
|
4605
4766
|
}
|
|
4606
4767
|
return;
|
|
4607
4768
|
}
|
|
4608
|
-
|
|
4609
|
-
|
|
4610
|
-
|
|
4611
|
-
|
|
4612
|
-
|
|
4613
|
-
|
|
4614
|
-
|
|
4615
|
-
|
|
4616
|
-
|
|
4617
|
-
|
|
4618
|
-
|
|
4619
|
-
|
|
4620
|
-
|
|
4621
|
-
|
|
4622
|
-
|
|
4769
|
+
const processMessage = async () => {
|
|
4770
|
+
if (!this.gateway) return;
|
|
4771
|
+
try {
|
|
4772
|
+
await ctx.api.sendChatAction(chatId, "typing");
|
|
4773
|
+
const response = await this.gateway.handleInboundMessage(
|
|
4774
|
+
{
|
|
4775
|
+
channel: "telegram",
|
|
4776
|
+
peerId: chatId,
|
|
4777
|
+
peerKind: isGroup ? "group" : "dm"
|
|
4778
|
+
},
|
|
4779
|
+
msg.text
|
|
4780
|
+
);
|
|
4781
|
+
if (response) {
|
|
4782
|
+
const chunks = splitMessage(response, 4e3);
|
|
4783
|
+
for (const chunk of chunks) {
|
|
4784
|
+
await ctx.api.sendMessage(chatId, chunk);
|
|
4785
|
+
}
|
|
4623
4786
|
}
|
|
4787
|
+
} catch (err) {
|
|
4788
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
4789
|
+
await ctx.api.sendMessage(chatId, `Error: ${errMsg.slice(0, 200)}`);
|
|
4624
4790
|
}
|
|
4625
|
-
}
|
|
4626
|
-
|
|
4627
|
-
|
|
4628
|
-
}
|
|
4791
|
+
};
|
|
4792
|
+
const prev = chatLocks.get(chatId) || Promise.resolve();
|
|
4793
|
+
const next = prev.then(processMessage).catch(() => {
|
|
4794
|
+
});
|
|
4795
|
+
chatLocks.set(chatId, next);
|
|
4629
4796
|
});
|
|
4630
4797
|
bot.start({
|
|
4631
4798
|
onStart: () => {
|
|
@@ -5172,7 +5339,7 @@ var init_server = __esm({
|
|
|
5172
5339
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
5173
5340
|
res.end(JSON.stringify({
|
|
5174
5341
|
status: "ok",
|
|
5175
|
-
version: "1.1
|
|
5342
|
+
version: "1.2.1",
|
|
5176
5343
|
uptime: process.uptime(),
|
|
5177
5344
|
clients: this.clients.size,
|
|
5178
5345
|
agents: this.engines.size
|
|
@@ -5280,7 +5447,7 @@ var init_server = __esm({
|
|
|
5280
5447
|
const hello = {
|
|
5281
5448
|
type: "hello",
|
|
5282
5449
|
protocol: PROTOCOL_VERSION,
|
|
5283
|
-
version: "1.1
|
|
5450
|
+
version: "1.2.1",
|
|
5284
5451
|
agents: this.config.agents.list.map((a) => ({
|
|
5285
5452
|
id: a.id,
|
|
5286
5453
|
name: a.name || a.id,
|
|
@@ -6614,7 +6781,7 @@ async function runTui(opts) {
|
|
|
6614
6781
|
ws.on("open", () => {
|
|
6615
6782
|
ws.send(JSON.stringify({
|
|
6616
6783
|
type: "connect",
|
|
6617
|
-
params: { auth: { token }, mode: "tui", version: "1.1
|
|
6784
|
+
params: { auth: { token }, mode: "tui", version: "1.2.1" }
|
|
6618
6785
|
}));
|
|
6619
6786
|
});
|
|
6620
6787
|
ws.on("message", (data) => {
|
|
@@ -6661,9 +6828,9 @@ async function runTui(opts) {
|
|
|
6661
6828
|
if (input.startsWith("!")) {
|
|
6662
6829
|
const cmd = input.slice(1).trim();
|
|
6663
6830
|
if (cmd) {
|
|
6664
|
-
const { execSync:
|
|
6831
|
+
const { execSync: execSync3 } = await import("child_process");
|
|
6665
6832
|
try {
|
|
6666
|
-
const out =
|
|
6833
|
+
const out = execSync3(cmd, { encoding: "utf-8", timeout: 3e4, env: { ...process.env, CLANK_SHELL: "tui-local" } });
|
|
6667
6834
|
console.log(out);
|
|
6668
6835
|
} catch (err) {
|
|
6669
6836
|
console.log(red5(err.stderr || err.message));
|
|
@@ -6899,6 +7066,63 @@ var init_tui = __esm({
|
|
|
6899
7066
|
}
|
|
6900
7067
|
});
|
|
6901
7068
|
|
|
7069
|
+
// src/cli/update.ts
|
|
7070
|
+
var update_exports = {};
|
|
7071
|
+
__export(update_exports, {
|
|
7072
|
+
runUpdate: () => runUpdate
|
|
7073
|
+
});
|
|
7074
|
+
import { execSync as execSync2 } from "child_process";
|
|
7075
|
+
async function runUpdate() {
|
|
7076
|
+
console.log("");
|
|
7077
|
+
console.log(dim9(" Updating Clank..."));
|
|
7078
|
+
console.log(dim9(" Stopping gateway..."));
|
|
7079
|
+
try {
|
|
7080
|
+
const { gatewayStop: gatewayStop2 } = await Promise.resolve().then(() => (init_gateway_cmd(), gateway_cmd_exports));
|
|
7081
|
+
await gatewayStop2();
|
|
7082
|
+
} catch {
|
|
7083
|
+
}
|
|
7084
|
+
console.log(dim9(" Pulling latest version..."));
|
|
7085
|
+
try {
|
|
7086
|
+
const output = execSync2("npm install -g @tractorscorch/clank@latest", {
|
|
7087
|
+
encoding: "utf-8",
|
|
7088
|
+
timeout: 12e4
|
|
7089
|
+
});
|
|
7090
|
+
console.log(dim9(` ${output.trim()}`));
|
|
7091
|
+
} catch (err) {
|
|
7092
|
+
console.error(red6(` Update failed: ${err instanceof Error ? err.message : err}`));
|
|
7093
|
+
console.error(dim9(" Try manually: npm install -g @tractorscorch/clank@latest"));
|
|
7094
|
+
return;
|
|
7095
|
+
}
|
|
7096
|
+
try {
|
|
7097
|
+
const newVersion = execSync2("clank --version", { encoding: "utf-8" }).trim();
|
|
7098
|
+
console.log(green9(` Updated to v${newVersion}`));
|
|
7099
|
+
} catch {
|
|
7100
|
+
console.log(green9(" Package updated"));
|
|
7101
|
+
}
|
|
7102
|
+
console.log(dim9(" Restarting gateway..."));
|
|
7103
|
+
try {
|
|
7104
|
+
const { gatewayStartBackground: gatewayStartBackground2 } = await Promise.resolve().then(() => (init_gateway_cmd(), gateway_cmd_exports));
|
|
7105
|
+
await gatewayStartBackground2();
|
|
7106
|
+
console.log(green9(" Gateway restarted"));
|
|
7107
|
+
} catch (err) {
|
|
7108
|
+
console.log(dim9(" Gateway not restarted. Start manually: clank gateway start"));
|
|
7109
|
+
}
|
|
7110
|
+
console.log("");
|
|
7111
|
+
console.log(green9(" Clank updated successfully."));
|
|
7112
|
+
console.log(dim9(" Config, sessions, and memory preserved."));
|
|
7113
|
+
console.log("");
|
|
7114
|
+
}
|
|
7115
|
+
var dim9, green9, red6;
|
|
7116
|
+
var init_update = __esm({
|
|
7117
|
+
"src/cli/update.ts"() {
|
|
7118
|
+
"use strict";
|
|
7119
|
+
init_esm_shims();
|
|
7120
|
+
dim9 = (s) => `\x1B[2m${s}\x1B[0m`;
|
|
7121
|
+
green9 = (s) => `\x1B[32m${s}\x1B[0m`;
|
|
7122
|
+
red6 = (s) => `\x1B[31m${s}\x1B[0m`;
|
|
7123
|
+
}
|
|
7124
|
+
});
|
|
7125
|
+
|
|
6902
7126
|
// src/cli/uninstall.ts
|
|
6903
7127
|
var uninstall_exports = {};
|
|
6904
7128
|
__export(uninstall_exports, {
|
|
@@ -6913,14 +7137,14 @@ async function runUninstall(opts) {
|
|
|
6913
7137
|
console.log(bold4(" Uninstall Clank"));
|
|
6914
7138
|
console.log("");
|
|
6915
7139
|
console.log(" This will permanently remove:");
|
|
6916
|
-
console.log(
|
|
6917
|
-
console.log(
|
|
6918
|
-
console.log(
|
|
6919
|
-
console.log(
|
|
6920
|
-
console.log(
|
|
6921
|
-
console.log(
|
|
6922
|
-
console.log(
|
|
6923
|
-
console.log(
|
|
7140
|
+
console.log(red7(` ${configDir}`));
|
|
7141
|
+
console.log(dim10(" \u251C\u2500\u2500 config.json5 (configuration)"));
|
|
7142
|
+
console.log(dim10(" \u251C\u2500\u2500 conversations/ (chat history)"));
|
|
7143
|
+
console.log(dim10(" \u251C\u2500\u2500 memory/ (agent memory)"));
|
|
7144
|
+
console.log(dim10(" \u251C\u2500\u2500 workspace/ (SOUL.md, USER.md, etc.)"));
|
|
7145
|
+
console.log(dim10(" \u251C\u2500\u2500 logs/ (gateway logs)"));
|
|
7146
|
+
console.log(dim10(" \u251C\u2500\u2500 cron/ (scheduled jobs)"));
|
|
7147
|
+
console.log(dim10(" \u2514\u2500\u2500 plugins/ (installed plugins)"));
|
|
6924
7148
|
console.log("");
|
|
6925
7149
|
if (!opts.yes) {
|
|
6926
7150
|
const rl = createInterface4({ input: process.stdin, output: process.stdout });
|
|
@@ -6929,51 +7153,51 @@ async function runUninstall(opts) {
|
|
|
6929
7153
|
});
|
|
6930
7154
|
rl.close();
|
|
6931
7155
|
if (answer.trim().toLowerCase() !== "y") {
|
|
6932
|
-
console.log(
|
|
7156
|
+
console.log(dim10(" Uninstall cancelled."));
|
|
6933
7157
|
return;
|
|
6934
7158
|
}
|
|
6935
7159
|
}
|
|
6936
|
-
console.log(
|
|
7160
|
+
console.log(dim10(" Stopping gateway..."));
|
|
6937
7161
|
try {
|
|
6938
7162
|
await gatewayStop();
|
|
6939
7163
|
} catch {
|
|
6940
7164
|
}
|
|
6941
|
-
console.log(
|
|
7165
|
+
console.log(dim10(" Removing system service..."));
|
|
6942
7166
|
try {
|
|
6943
7167
|
const { uninstallDaemon: uninstallDaemon2 } = await Promise.resolve().then(() => (init_daemon(), daemon_exports));
|
|
6944
7168
|
await uninstallDaemon2();
|
|
6945
7169
|
} catch {
|
|
6946
7170
|
}
|
|
6947
|
-
console.log(
|
|
7171
|
+
console.log(dim10(" Deleting data..."));
|
|
6948
7172
|
if (existsSync9(configDir)) {
|
|
6949
7173
|
await rm(configDir, { recursive: true, force: true });
|
|
6950
|
-
console.log(
|
|
7174
|
+
console.log(green10(` Removed ${configDir}`));
|
|
6951
7175
|
} else {
|
|
6952
|
-
console.log(
|
|
7176
|
+
console.log(dim10(" No data directory found."));
|
|
6953
7177
|
}
|
|
6954
|
-
console.log(
|
|
7178
|
+
console.log(dim10(" Uninstalling npm package..."));
|
|
6955
7179
|
try {
|
|
6956
|
-
const { execSync:
|
|
6957
|
-
|
|
6958
|
-
console.log(
|
|
7180
|
+
const { execSync: execSync3 } = await import("child_process");
|
|
7181
|
+
execSync3("npm uninstall -g @tractorscorch/clank", { stdio: "ignore" });
|
|
7182
|
+
console.log(green10(" npm package uninstalled"));
|
|
6959
7183
|
} catch {
|
|
6960
|
-
console.log(
|
|
7184
|
+
console.log(dim10(" Could not uninstall npm package (may not be globally installed)"));
|
|
6961
7185
|
}
|
|
6962
7186
|
console.log("");
|
|
6963
|
-
console.log(
|
|
7187
|
+
console.log(green10(" Clank has been completely removed."));
|
|
6964
7188
|
console.log("");
|
|
6965
7189
|
}
|
|
6966
|
-
var
|
|
7190
|
+
var dim10, bold4, green10, red7, yellow4;
|
|
6967
7191
|
var init_uninstall = __esm({
|
|
6968
7192
|
"src/cli/uninstall.ts"() {
|
|
6969
7193
|
"use strict";
|
|
6970
7194
|
init_esm_shims();
|
|
6971
7195
|
init_config2();
|
|
6972
7196
|
init_gateway_cmd();
|
|
6973
|
-
|
|
7197
|
+
dim10 = (s) => `\x1B[2m${s}\x1B[0m`;
|
|
6974
7198
|
bold4 = (s) => `\x1B[1m${s}\x1B[0m`;
|
|
6975
|
-
|
|
6976
|
-
|
|
7199
|
+
green10 = (s) => `\x1B[32m${s}\x1B[0m`;
|
|
7200
|
+
red7 = (s) => `\x1B[31m${s}\x1B[0m`;
|
|
6977
7201
|
yellow4 = (s) => `\x1B[33m${s}\x1B[0m`;
|
|
6978
7202
|
}
|
|
6979
7203
|
});
|
|
@@ -6986,7 +7210,7 @@ import { fileURLToPath as fileURLToPath5 } from "url";
|
|
|
6986
7210
|
import { dirname as dirname5, join as join18 } from "path";
|
|
6987
7211
|
var __filename3 = fileURLToPath5(import.meta.url);
|
|
6988
7212
|
var __dirname3 = dirname5(__filename3);
|
|
6989
|
-
var version = "1.1
|
|
7213
|
+
var version = "1.2.1";
|
|
6990
7214
|
try {
|
|
6991
7215
|
const pkg = JSON.parse(readFileSync(join18(__dirname3, "..", "package.json"), "utf-8"));
|
|
6992
7216
|
version = pkg.version;
|
|
@@ -7145,6 +7369,10 @@ program.command("channels").description("Show channel adapter status").action(as
|
|
|
7145
7369
|
}
|
|
7146
7370
|
if (Object.keys(channels).length === 0) console.log(" (none configured)");
|
|
7147
7371
|
});
|
|
7372
|
+
program.command("update").description("Update Clank to the latest version and restart gateway").action(async () => {
|
|
7373
|
+
const { runUpdate: runUpdate2 } = await Promise.resolve().then(() => (init_update(), update_exports));
|
|
7374
|
+
await runUpdate2();
|
|
7375
|
+
});
|
|
7148
7376
|
program.command("uninstall").description("Remove Clank completely (config, data, service, package)").option("-y, --yes", "Skip confirmation prompt").action(async (opts) => {
|
|
7149
7377
|
const { runUninstall: runUninstall2 } = await Promise.resolve().then(() => (init_uninstall(), uninstall_exports));
|
|
7150
7378
|
await runUninstall2(opts);
|