@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 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
- * Get the context utilization as a percentage.
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
- return this.estimateTokens() / this.contextWindowSize * 100;
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%) since they have larger windows.
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
- * Compact the context to fit within the context window.
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
- for (const msg of compactable) {
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
- while (this.estimateTokens() > this.contextWindowSize * 0.7 && this.messages.length > protectedCount + 2) {
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
- return {
136
- ok: true,
137
- messagesBefore: before,
138
- messagesAfter: this.messages.length,
139
- estimatedTokensBefore: tokensBefore,
140
- estimatedTokensAfter: this.estimateTokens()
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 (e.g., after detecting it from Ollama) */
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.compact();
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
- if (!this.gateway) return;
4609
- try {
4610
- await ctx.api.sendChatAction(chatId, "typing");
4611
- const response = await this.gateway.handleInboundMessage(
4612
- {
4613
- channel: "telegram",
4614
- peerId: chatId,
4615
- peerKind: isGroup ? "group" : "dm"
4616
- },
4617
- msg.text
4618
- );
4619
- if (response) {
4620
- const chunks = splitMessage(response, 4e3);
4621
- for (const chunk of chunks) {
4622
- await ctx.api.sendMessage(chatId, chunk);
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
- } catch (err) {
4626
- const errMsg = err instanceof Error ? err.message : String(err);
4627
- await ctx.api.sendMessage(chatId, `Error: ${errMsg.slice(0, 200)}`);
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.0",
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.0",
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.0" }
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: execSync2 } = await import("child_process");
6831
+ const { execSync: execSync3 } = await import("child_process");
6665
6832
  try {
6666
- const out = execSync2(cmd, { encoding: "utf-8", timeout: 3e4, env: { ...process.env, CLANK_SHELL: "tui-local" } });
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(red6(` ${configDir}`));
6917
- console.log(dim9(" \u251C\u2500\u2500 config.json5 (configuration)"));
6918
- console.log(dim9(" \u251C\u2500\u2500 conversations/ (chat history)"));
6919
- console.log(dim9(" \u251C\u2500\u2500 memory/ (agent memory)"));
6920
- console.log(dim9(" \u251C\u2500\u2500 workspace/ (SOUL.md, USER.md, etc.)"));
6921
- console.log(dim9(" \u251C\u2500\u2500 logs/ (gateway logs)"));
6922
- console.log(dim9(" \u251C\u2500\u2500 cron/ (scheduled jobs)"));
6923
- console.log(dim9(" \u2514\u2500\u2500 plugins/ (installed plugins)"));
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(dim9(" Uninstall cancelled."));
7156
+ console.log(dim10(" Uninstall cancelled."));
6933
7157
  return;
6934
7158
  }
6935
7159
  }
6936
- console.log(dim9(" Stopping gateway..."));
7160
+ console.log(dim10(" Stopping gateway..."));
6937
7161
  try {
6938
7162
  await gatewayStop();
6939
7163
  } catch {
6940
7164
  }
6941
- console.log(dim9(" Removing system service..."));
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(dim9(" Deleting data..."));
7171
+ console.log(dim10(" Deleting data..."));
6948
7172
  if (existsSync9(configDir)) {
6949
7173
  await rm(configDir, { recursive: true, force: true });
6950
- console.log(green9(` Removed ${configDir}`));
7174
+ console.log(green10(` Removed ${configDir}`));
6951
7175
  } else {
6952
- console.log(dim9(" No data directory found."));
7176
+ console.log(dim10(" No data directory found."));
6953
7177
  }
6954
- console.log(dim9(" Uninstalling npm package..."));
7178
+ console.log(dim10(" Uninstalling npm package..."));
6955
7179
  try {
6956
- const { execSync: execSync2 } = await import("child_process");
6957
- execSync2("npm uninstall -g clank", { stdio: "ignore" });
6958
- console.log(green9(" npm package uninstalled"));
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(dim9(" Could not uninstall npm package (may not be globally installed)"));
7184
+ console.log(dim10(" Could not uninstall npm package (may not be globally installed)"));
6961
7185
  }
6962
7186
  console.log("");
6963
- console.log(green9(" Clank has been completely removed."));
7187
+ console.log(green10(" Clank has been completely removed."));
6964
7188
  console.log("");
6965
7189
  }
6966
- var dim9, bold4, green9, red6, yellow4;
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
- dim9 = (s) => `\x1B[2m${s}\x1B[0m`;
7197
+ dim10 = (s) => `\x1B[2m${s}\x1B[0m`;
6974
7198
  bold4 = (s) => `\x1B[1m${s}\x1B[0m`;
6975
- green9 = (s) => `\x1B[32m${s}\x1B[0m`;
6976
- red6 = (s) => `\x1B[31m${s}\x1B[0m`;
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.0";
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);