@tractorscorch/clank 1.1.0 → 1.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/CHANGELOG.md CHANGED
@@ -6,6 +6,17 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/).
6
6
 
7
7
  ---
8
8
 
9
+ ## [1.2.0] — 2026-03-22
10
+
11
+ ### Added
12
+ - **Two-tier context compaction** — critical for local model performance:
13
+ - Tier 1 (fast): system prompt budgeting, tool result dedup, message truncation, aggressive dropping
14
+ - Tier 2 (LLM-summarized): model generates conversation recap replacing oldest messages. Preserves meaning over long sessions.
15
+ - Token budgeting: reserves 25% for response, budgets system prompt separately from conversation
16
+ - **`clank update`** — update to latest npm version, preserves config/sessions/memory, restarts gateway
17
+
18
+ ---
19
+
9
20
  ## [1.1.0] — 2026-03-22
10
21
 
11
22
  ### 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,
@@ -5172,7 +5327,7 @@ var init_server = __esm({
5172
5327
  res.writeHead(200, { "Content-Type": "application/json" });
5173
5328
  res.end(JSON.stringify({
5174
5329
  status: "ok",
5175
- version: "1.1.0",
5330
+ version: "1.2.0",
5176
5331
  uptime: process.uptime(),
5177
5332
  clients: this.clients.size,
5178
5333
  agents: this.engines.size
@@ -5280,7 +5435,7 @@ var init_server = __esm({
5280
5435
  const hello = {
5281
5436
  type: "hello",
5282
5437
  protocol: PROTOCOL_VERSION,
5283
- version: "1.1.0",
5438
+ version: "1.2.0",
5284
5439
  agents: this.config.agents.list.map((a) => ({
5285
5440
  id: a.id,
5286
5441
  name: a.name || a.id,
@@ -6614,7 +6769,7 @@ async function runTui(opts) {
6614
6769
  ws.on("open", () => {
6615
6770
  ws.send(JSON.stringify({
6616
6771
  type: "connect",
6617
- params: { auth: { token }, mode: "tui", version: "1.1.0" }
6772
+ params: { auth: { token }, mode: "tui", version: "1.2.0" }
6618
6773
  }));
6619
6774
  });
6620
6775
  ws.on("message", (data) => {
@@ -6661,9 +6816,9 @@ async function runTui(opts) {
6661
6816
  if (input.startsWith("!")) {
6662
6817
  const cmd = input.slice(1).trim();
6663
6818
  if (cmd) {
6664
- const { execSync: execSync2 } = await import("child_process");
6819
+ const { execSync: execSync3 } = await import("child_process");
6665
6820
  try {
6666
- const out = execSync2(cmd, { encoding: "utf-8", timeout: 3e4, env: { ...process.env, CLANK_SHELL: "tui-local" } });
6821
+ const out = execSync3(cmd, { encoding: "utf-8", timeout: 3e4, env: { ...process.env, CLANK_SHELL: "tui-local" } });
6667
6822
  console.log(out);
6668
6823
  } catch (err) {
6669
6824
  console.log(red5(err.stderr || err.message));
@@ -6899,6 +7054,63 @@ var init_tui = __esm({
6899
7054
  }
6900
7055
  });
6901
7056
 
7057
+ // src/cli/update.ts
7058
+ var update_exports = {};
7059
+ __export(update_exports, {
7060
+ runUpdate: () => runUpdate
7061
+ });
7062
+ import { execSync as execSync2 } from "child_process";
7063
+ async function runUpdate() {
7064
+ console.log("");
7065
+ console.log(dim9(" Updating Clank..."));
7066
+ console.log(dim9(" Stopping gateway..."));
7067
+ try {
7068
+ const { gatewayStop: gatewayStop2 } = await Promise.resolve().then(() => (init_gateway_cmd(), gateway_cmd_exports));
7069
+ await gatewayStop2();
7070
+ } catch {
7071
+ }
7072
+ console.log(dim9(" Pulling latest version..."));
7073
+ try {
7074
+ const output = execSync2("npm install -g @tractorscorch/clank@latest", {
7075
+ encoding: "utf-8",
7076
+ timeout: 12e4
7077
+ });
7078
+ console.log(dim9(` ${output.trim()}`));
7079
+ } catch (err) {
7080
+ console.error(red6(` Update failed: ${err instanceof Error ? err.message : err}`));
7081
+ console.error(dim9(" Try manually: npm install -g @tractorscorch/clank@latest"));
7082
+ return;
7083
+ }
7084
+ try {
7085
+ const newVersion = execSync2("clank --version", { encoding: "utf-8" }).trim();
7086
+ console.log(green9(` Updated to v${newVersion}`));
7087
+ } catch {
7088
+ console.log(green9(" Package updated"));
7089
+ }
7090
+ console.log(dim9(" Restarting gateway..."));
7091
+ try {
7092
+ const { gatewayStartBackground: gatewayStartBackground2 } = await Promise.resolve().then(() => (init_gateway_cmd(), gateway_cmd_exports));
7093
+ await gatewayStartBackground2();
7094
+ console.log(green9(" Gateway restarted"));
7095
+ } catch (err) {
7096
+ console.log(dim9(" Gateway not restarted. Start manually: clank gateway start"));
7097
+ }
7098
+ console.log("");
7099
+ console.log(green9(" Clank updated successfully."));
7100
+ console.log(dim9(" Config, sessions, and memory preserved."));
7101
+ console.log("");
7102
+ }
7103
+ var dim9, green9, red6;
7104
+ var init_update = __esm({
7105
+ "src/cli/update.ts"() {
7106
+ "use strict";
7107
+ init_esm_shims();
7108
+ dim9 = (s) => `\x1B[2m${s}\x1B[0m`;
7109
+ green9 = (s) => `\x1B[32m${s}\x1B[0m`;
7110
+ red6 = (s) => `\x1B[31m${s}\x1B[0m`;
7111
+ }
7112
+ });
7113
+
6902
7114
  // src/cli/uninstall.ts
6903
7115
  var uninstall_exports = {};
6904
7116
  __export(uninstall_exports, {
@@ -6913,14 +7125,14 @@ async function runUninstall(opts) {
6913
7125
  console.log(bold4(" Uninstall Clank"));
6914
7126
  console.log("");
6915
7127
  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)"));
7128
+ console.log(red7(` ${configDir}`));
7129
+ console.log(dim10(" \u251C\u2500\u2500 config.json5 (configuration)"));
7130
+ console.log(dim10(" \u251C\u2500\u2500 conversations/ (chat history)"));
7131
+ console.log(dim10(" \u251C\u2500\u2500 memory/ (agent memory)"));
7132
+ console.log(dim10(" \u251C\u2500\u2500 workspace/ (SOUL.md, USER.md, etc.)"));
7133
+ console.log(dim10(" \u251C\u2500\u2500 logs/ (gateway logs)"));
7134
+ console.log(dim10(" \u251C\u2500\u2500 cron/ (scheduled jobs)"));
7135
+ console.log(dim10(" \u2514\u2500\u2500 plugins/ (installed plugins)"));
6924
7136
  console.log("");
6925
7137
  if (!opts.yes) {
6926
7138
  const rl = createInterface4({ input: process.stdin, output: process.stdout });
@@ -6929,51 +7141,51 @@ async function runUninstall(opts) {
6929
7141
  });
6930
7142
  rl.close();
6931
7143
  if (answer.trim().toLowerCase() !== "y") {
6932
- console.log(dim9(" Uninstall cancelled."));
7144
+ console.log(dim10(" Uninstall cancelled."));
6933
7145
  return;
6934
7146
  }
6935
7147
  }
6936
- console.log(dim9(" Stopping gateway..."));
7148
+ console.log(dim10(" Stopping gateway..."));
6937
7149
  try {
6938
7150
  await gatewayStop();
6939
7151
  } catch {
6940
7152
  }
6941
- console.log(dim9(" Removing system service..."));
7153
+ console.log(dim10(" Removing system service..."));
6942
7154
  try {
6943
7155
  const { uninstallDaemon: uninstallDaemon2 } = await Promise.resolve().then(() => (init_daemon(), daemon_exports));
6944
7156
  await uninstallDaemon2();
6945
7157
  } catch {
6946
7158
  }
6947
- console.log(dim9(" Deleting data..."));
7159
+ console.log(dim10(" Deleting data..."));
6948
7160
  if (existsSync9(configDir)) {
6949
7161
  await rm(configDir, { recursive: true, force: true });
6950
- console.log(green9(` Removed ${configDir}`));
7162
+ console.log(green10(` Removed ${configDir}`));
6951
7163
  } else {
6952
- console.log(dim9(" No data directory found."));
7164
+ console.log(dim10(" No data directory found."));
6953
7165
  }
6954
- console.log(dim9(" Uninstalling npm package..."));
7166
+ console.log(dim10(" Uninstalling npm package..."));
6955
7167
  try {
6956
- const { execSync: execSync2 } = await import("child_process");
6957
- execSync2("npm uninstall -g clank", { stdio: "ignore" });
6958
- console.log(green9(" npm package uninstalled"));
7168
+ const { execSync: execSync3 } = await import("child_process");
7169
+ execSync3("npm uninstall -g @tractorscorch/clank", { stdio: "ignore" });
7170
+ console.log(green10(" npm package uninstalled"));
6959
7171
  } catch {
6960
- console.log(dim9(" Could not uninstall npm package (may not be globally installed)"));
7172
+ console.log(dim10(" Could not uninstall npm package (may not be globally installed)"));
6961
7173
  }
6962
7174
  console.log("");
6963
- console.log(green9(" Clank has been completely removed."));
7175
+ console.log(green10(" Clank has been completely removed."));
6964
7176
  console.log("");
6965
7177
  }
6966
- var dim9, bold4, green9, red6, yellow4;
7178
+ var dim10, bold4, green10, red7, yellow4;
6967
7179
  var init_uninstall = __esm({
6968
7180
  "src/cli/uninstall.ts"() {
6969
7181
  "use strict";
6970
7182
  init_esm_shims();
6971
7183
  init_config2();
6972
7184
  init_gateway_cmd();
6973
- dim9 = (s) => `\x1B[2m${s}\x1B[0m`;
7185
+ dim10 = (s) => `\x1B[2m${s}\x1B[0m`;
6974
7186
  bold4 = (s) => `\x1B[1m${s}\x1B[0m`;
6975
- green9 = (s) => `\x1B[32m${s}\x1B[0m`;
6976
- red6 = (s) => `\x1B[31m${s}\x1B[0m`;
7187
+ green10 = (s) => `\x1B[32m${s}\x1B[0m`;
7188
+ red7 = (s) => `\x1B[31m${s}\x1B[0m`;
6977
7189
  yellow4 = (s) => `\x1B[33m${s}\x1B[0m`;
6978
7190
  }
6979
7191
  });
@@ -6986,7 +7198,7 @@ import { fileURLToPath as fileURLToPath5 } from "url";
6986
7198
  import { dirname as dirname5, join as join18 } from "path";
6987
7199
  var __filename3 = fileURLToPath5(import.meta.url);
6988
7200
  var __dirname3 = dirname5(__filename3);
6989
- var version = "1.1.0";
7201
+ var version = "1.2.0";
6990
7202
  try {
6991
7203
  const pkg = JSON.parse(readFileSync(join18(__dirname3, "..", "package.json"), "utf-8"));
6992
7204
  version = pkg.version;
@@ -7145,6 +7357,10 @@ program.command("channels").description("Show channel adapter status").action(as
7145
7357
  }
7146
7358
  if (Object.keys(channels).length === 0) console.log(" (none configured)");
7147
7359
  });
7360
+ program.command("update").description("Update Clank to the latest version and restart gateway").action(async () => {
7361
+ const { runUpdate: runUpdate2 } = await Promise.resolve().then(() => (init_update(), update_exports));
7362
+ await runUpdate2();
7363
+ });
7148
7364
  program.command("uninstall").description("Remove Clank completely (config, data, service, package)").option("-y, --yes", "Skip confirmation prompt").action(async (opts) => {
7149
7365
  const { runUninstall: runUninstall2 } = await Promise.resolve().then(() => (init_uninstall(), uninstall_exports));
7150
7366
  await runUninstall2(opts);