hostwares-cli 2.4.3 → 2.4.5

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.
Files changed (2) hide show
  1. package/dist/index.js +92 -171
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1877,12 +1877,6 @@ async function runTurn(opts) {
1877
1877
  let assistantContent = null;
1878
1878
  let toolResults = [];
1879
1879
  let firstHop = true;
1880
- const callCounts = /* @__PURE__ */ new Map();
1881
- const writeCounts = /* @__PURE__ */ new Map();
1882
- let stalledHops = 0;
1883
- let totalGuardHits = 0;
1884
- let emptyHops = 0;
1885
- const MAX_EMPTY_RETRIES = 1;
1886
1880
  const steering = buildSteeringContext(host.cwd ?? process.cwd(), opts.message);
1887
1881
  const firstMessage = steering ? `${steering}
1888
1882
 
@@ -1897,7 +1891,6 @@ ${opts.message}` : opts.message;
1897
1891
  throw new AbortedError();
1898
1892
  emit({ type: "thinking" });
1899
1893
  const pending = [];
1900
- const textBefore = text.length;
1901
1894
  const done = await streamChat(credentials, {
1902
1895
  ...firstHop ? { message: firstMessage, images: opts.images } : { toolResults, assistantContent },
1903
1896
  conversationId,
@@ -1952,113 +1945,15 @@ ${opts.message}` : opts.message;
1952
1945
  }
1953
1946
  }
1954
1947
  firstHop = false;
1955
- if (!pending.length) {
1956
- const producedText = text.length > textBefore;
1957
- if (!producedText && hop > 0 && emptyHops < MAX_EMPTY_RETRIES) {
1958
- emptyHops++;
1959
- toolResults = [{
1960
- id: `empty-${hop}`,
1961
- result: `[continue] Your last turn was empty \u2014 no message and no tool call. Do NOT stop here. Look at what has been done so far in this conversation and take the NEXT concrete step toward the goal: create the next file, run the next command, or if the task is genuinely complete, give a short final answer stating what you built and how to run it. Act now.`
1962
- }];
1963
- assistantContent = assistantContent ?? "";
1964
- continue;
1965
- }
1966
- if (!producedText && emptyHops >= MAX_EMPTY_RETRIES) {
1967
- const wroteSomething = writeCounts.size > 0;
1968
- emit({ type: "error", message: wroteSomething ? `Stopping: the last steps returned nothing new. What I changed is saved \u2014 tell me the specific next change and I'll make it directly.` : `Stopping: I couldn't make progress on that. Tell me the specific step you want (e.g. "create the server file") and I'll do it directly.` });
1969
- }
1948
+ if (!pending.length)
1970
1949
  break;
1950
+ const ran = await executePending(pending, opts, emit, checkpoint);
1951
+ if (ran === null) {
1952
+ return finish({ conversationId, turnCredits, balance, text, aborted: true });
1971
1953
  }
1972
- emptyHops = 0;
1973
- const toRun = [];
1974
- const shortCircuited = [];
1975
- for (const call of pending) {
1976
- const fp = `${call.name}:${stableStringify(call.input)}`;
1977
- const seen = (callCounts.get(fp) ?? 0) + 1;
1978
- callCounts.set(fp, seen);
1979
- const path = typeof call.input?.path === "string" ? call.input.path : void 0;
1980
- if (seen > MAX_IDENTICAL_CALLS && isReadOnlyTool(call.name)) {
1981
- shortCircuited.push({
1982
- id: call.id,
1983
- result: `[loop guard] You already ran ${call.name}(${compactArgs(call.input)}) and nothing has changed since \u2014 re-reading it will keep returning the same thing. This call was NOT executed. STOP re-inspecting and DECIDE from what you already know: if a file the task needs does not exist yet, WRITE it now (write_file) \u2014 an empty or missing directory means the file has not been created, not that you should look again. If every file the task needs already exists and has been run/verified, give your final answer now (what you built + how to run it) and end. Do NOT read or list again.`
1984
- });
1985
- emit({ type: "tool:end", id: call.id, tool: call.name, ok: false, durationMs: 0, summary: "loop guard: repeated read skipped" });
1986
- } else if (WRITE_TOOLS.has(call.name) && path && (writeCounts.get(path) ?? 0) >= MAX_WRITES_PER_FILE) {
1987
- shortCircuited.push({
1988
- id: call.id,
1989
- result: `[loop guard] You have already written ${path} ${writeCounts.get(path)} times this turn \u2014 its content is on disk. This write was NOT executed. Stop rewriting it: verify it (run it / typecheck it) or give your final answer. Do not write this file again unless you are making a specific, different change.`
1990
- });
1991
- emit({ type: "tool:end", id: call.id, tool: call.name, ok: false, durationMs: 0, summary: "loop guard: write thrash skipped" });
1992
- } else {
1993
- toRun.push(call);
1994
- }
1995
- }
1996
- totalGuardHits += shortCircuited.length;
1997
- if (toRun.length === 0 && shortCircuited.length > 0)
1998
- stalledHops++;
1999
- else
2000
- stalledHops = 0;
2001
- const wroteThisHop = toRun.some((c2) => WRITE_TOOLS.has(c2.name));
2002
- if (wroteThisHop) {
2003
- stalledHops = 0;
2004
- }
2005
- const filesWritten = writeCounts.size;
2006
- const guardHitCeiling = 6 + filesWritten * 3;
2007
- const stallCeiling = filesWritten > 0 ? 4 : 2;
2008
- if (stalledHops >= stallCeiling || totalGuardHits >= guardHitCeiling) {
2009
- const wroteSomething = filesWritten > 0;
2010
- emit({ type: "error", message: wroteSomething ? `Stopping: I kept re-reading the same files without making new progress. What I built so far is saved. Tell me the specific next change (e.g. "write the UI file" / "start it") and I'll do it directly.` : `Stopping: I kept re-reading the same files without writing anything. I have enough context now \u2014 tell me the specific change you want and I'll make it directly.` });
2011
- return finish({ conversationId, turnCredits, balance, text, aborted: false });
2012
- }
2013
- for (const c2 of toRun) {
2014
- if (!MUTATION_TOOLS.has(c2.name))
2015
- continue;
2016
- const p = typeof c2.input?.path === "string" ? c2.input.path : void 0;
2017
- if (!p)
2018
- continue;
2019
- if (WRITE_TOOLS.has(c2.name))
2020
- writeCounts.set(p, (writeCounts.get(p) ?? 0) + 1);
2021
- for (const key of [...callCounts.keys()]) {
2022
- if (key.includes(JSON.stringify(p)))
2023
- callCounts.delete(key);
2024
- }
2025
- }
2026
- let results = shortCircuited;
2027
- if (toRun.length) {
2028
- const ran = await executePending(toRun, opts, emit, checkpoint);
2029
- if (ran === null) {
2030
- return finish({ conversationId, turnCredits, balance, text, aborted: true });
2031
- }
2032
- for (const call of toRun) {
2033
- if (!WRITE_TOOLS.has(call.name))
2034
- continue;
2035
- const res = ran.find((r) => r.id === call.id);
2036
- if (!res || /^Error:/i.test(res.result))
2037
- continue;
2038
- const path = String(call.input?.path ?? "");
2039
- if (!path)
2040
- continue;
2041
- let hookResults;
2042
- try {
2043
- hookResults = await runPostWriteHooks(host.cwd ?? process.cwd(), path);
2044
- } catch {
2045
- hookResults = [];
2046
- }
2047
- for (const h of hookResults) {
2048
- emit({ type: "tool:end", id: `${call.id}:${h.name}`, tool: h.name, ok: h.ok, durationMs: 0, summary: h.ok ? `${h.name} passed` : `${h.name} failed` });
2049
- res.result += h.ok ? `
2050
-
2051
- [${h.name}] passed on ${path}.` : `
2052
-
2053
- [${h.name}] FAILED after this write \u2014 fix before continuing:
2054
- ${h.output}`;
2055
- }
2056
- }
2057
- results = [...shortCircuited, ...ran];
2058
- }
2059
- toolResults = results;
1954
+ toolResults = ran;
2060
1955
  if (hop === MAX_HOPS - 1) {
2061
- emit({ type: "error", message: `Paused after ${MAX_HOPS} steps to check in - this is a long task, not necessarily a finished one. Say "continue" and I'll pick up exactly where I left off.` });
1956
+ emit({ type: "error", message: `Paused after ${MAX_HOPS} steps to check in \u2014 this is a long task, not necessarily a finished one. Say "continue" and I'll pick up exactly where I left off.` });
2062
1957
  }
2063
1958
  }
2064
1959
  emit({
@@ -2097,21 +1992,6 @@ function finish(r) {
2097
1992
  error: r.error
2098
1993
  };
2099
1994
  }
2100
- function isReadOnlyTool(name) {
2101
- return READ_ONLY_TOOLS.has(name);
2102
- }
2103
- function stableStringify(v) {
2104
- if (v === null || typeof v !== "object")
2105
- return JSON.stringify(v);
2106
- if (Array.isArray(v))
2107
- return `[${v.map(stableStringify).join(",")}]`;
2108
- const o = v;
2109
- return `{${Object.keys(o).sort().map((k) => `${JSON.stringify(k)}:${stableStringify(o[k])}`).join(",")}}`;
2110
- }
2111
- function compactArgs(input) {
2112
- const s = Object.entries(input).map(([k, val]) => `${k}: ${JSON.stringify(val)}`).join(", ");
2113
- return s.length > 120 ? `${s.slice(0, 117)}...` : s;
2114
- }
2115
1995
  async function executePending(calls, opts, emit, checkpoint) {
2116
1996
  const results = [];
2117
1997
  for (const call of calls) {
@@ -2151,14 +2031,14 @@ async function executePending(calls, opts, emit, checkpoint) {
2151
2031
  let oldContent = null;
2152
2032
  if (WRITE_TOOLS.has(call.name) && typeof call.input?.path === "string" && call.input.path) {
2153
2033
  try {
2154
- const { readFileSync: readFileSync9, existsSync: existsSync8 } = await import("fs");
2155
- const { resolve: pathResolve2, join: join9 } = await import("path");
2156
- const { homedir: homedir5 } = await import("os");
2034
+ const { readFileSync: readFileSync10, existsSync: existsSync9 } = await import("fs");
2035
+ const { resolve: pathResolve2, join: join10 } = await import("path");
2036
+ const { homedir: homedir6 } = await import("os");
2157
2037
  const raw = String(call.input.path);
2158
- const expanded = raw === "~" ? homedir5() : raw.startsWith("~/") ? join9(homedir5(), raw.slice(2)) : raw;
2038
+ const expanded = raw === "~" ? homedir6() : raw.startsWith("~/") ? join10(homedir6(), raw.slice(2)) : raw;
2159
2039
  const abs = pathResolve2(expanded);
2160
- if (existsSync8(abs))
2161
- oldContent = readFileSync9(abs, "utf8");
2040
+ if (existsSync9(abs))
2041
+ oldContent = readFileSync10(abs, "utf8");
2162
2042
  else
2163
2043
  oldContent = "";
2164
2044
  } catch {
@@ -2199,7 +2079,25 @@ async function executePending(calls, opts, emit, checkpoint) {
2199
2079
  }
2200
2080
  const bytes = effect.kind === "wrote" || effect.kind === "modified" ? byteLength(call.input?.content) : void 0;
2201
2081
  emit({ type: "tool:end", id: call.id, tool: call.name, ok: !failed2, durationMs: Date.now() - started, effect, bytes });
2202
- results.push({ id: call.id, result: truncate(output) });
2082
+ let resultText = output;
2083
+ if (!failed2 && WRITE_TOOLS.has(call.name) && typeof call.input?.path === "string" && call.input.path) {
2084
+ let hookResults;
2085
+ try {
2086
+ hookResults = await runPostWriteHooks(opts.host.cwd ?? process.cwd(), String(call.input.path));
2087
+ } catch {
2088
+ hookResults = [];
2089
+ }
2090
+ for (const h of hookResults) {
2091
+ emit({ type: "tool:end", id: `${call.id}:${h.name}`, tool: h.name, ok: h.ok, durationMs: 0, summary: h.ok ? `${h.name} passed` : `${h.name} failed` });
2092
+ resultText += h.ok ? `
2093
+
2094
+ [${h.name}] passed on ${call.input.path}.` : `
2095
+
2096
+ [${h.name}] FAILED after this write \u2014 fix before continuing:
2097
+ ${h.output}`;
2098
+ }
2099
+ }
2100
+ results.push({ id: call.id, result: truncate(resultText) });
2203
2101
  }
2204
2102
  return results;
2205
2103
  }
@@ -2247,7 +2145,7 @@ function targetOf(call) {
2247
2145
  return void 0;
2248
2146
  return s.length > 60 ? `${s.slice(0, 57)}\u2026` : s;
2249
2147
  }
2250
- var MAX_HOPS, MAX_IDENTICAL_CALLS, MAX_WRITES_PER_FILE, MAX_RESULT_CHARS, READ_ONLY_TOOLS, MUTATION_TOOLS, WRITE_TOOLS;
2148
+ var MAX_HOPS, MAX_RESULT_CHARS, MUTATION_TOOLS, WRITE_TOOLS;
2251
2149
  var init_loop = __esm({
2252
2150
  "node_modules/@hostwares/agent-client/dist/esm/agent/loop.js"() {
2253
2151
  init_client();
@@ -2259,28 +2157,13 @@ var init_loop = __esm({
2259
2157
  init_subagents();
2260
2158
  init_safety();
2261
2159
  init_events();
2262
- MAX_HOPS = 50;
2263
- MAX_IDENTICAL_CALLS = 2;
2264
- MAX_WRITES_PER_FILE = 2;
2160
+ MAX_HOPS = 80;
2265
2161
  MAX_RESULT_CHARS = 3e4;
2266
- READ_ONLY_TOOLS = /* @__PURE__ */ new Set([
2267
- "read_file",
2268
- "list_directory",
2269
- "search_files",
2270
- "file_search",
2271
- "get_system_info",
2272
- "list_processes",
2273
- "get_process_output",
2274
- "check_port",
2275
- "git_status",
2276
- "git_log",
2277
- "git_diff",
2278
- "docker_ps"
2279
- ]);
2280
2162
  MUTATION_TOOLS = /* @__PURE__ */ new Set([
2281
2163
  "write_file",
2282
2164
  "str_replace_file",
2283
2165
  "append_file",
2166
+ "edit_file",
2284
2167
  "delete_path",
2285
2168
  "git_commit",
2286
2169
  "git_add",
@@ -2290,7 +2173,7 @@ var init_loop = __esm({
2290
2173
  "stop_process",
2291
2174
  "kill_process"
2292
2175
  ]);
2293
- WRITE_TOOLS = /* @__PURE__ */ new Set(["write_file", "str_replace_file", "append_file"]);
2176
+ WRITE_TOOLS = /* @__PURE__ */ new Set(["write_file", "str_replace_file", "append_file", "edit_file"]);
2294
2177
  }
2295
2178
  });
2296
2179
 
@@ -2838,9 +2721,9 @@ var init_device = __esm({
2838
2721
  });
2839
2722
 
2840
2723
  // src/index.ts
2841
- import { readFileSync as readFileSync8 } from "fs";
2724
+ import { readFileSync as readFileSync9 } from "fs";
2842
2725
  import { fileURLToPath } from "url";
2843
- import { dirname as dirname4, join as join8 } from "path";
2726
+ import { dirname as dirname4, join as join9 } from "path";
2844
2727
 
2845
2728
  // src/agent/run.ts
2846
2729
  init_esm();
@@ -3000,6 +2883,9 @@ async function pickScope(options) {
3000
2883
  init_theme();
3001
2884
  init_render();
3002
2885
  init_esm();
2886
+ import { existsSync as existsSync7, readFileSync as readFileSync7, writeFileSync as writeFileSync5, mkdirSync as mkdirSync5, renameSync as renameSync2, chmodSync as chmodSync2 } from "fs";
2887
+ import { join as join7 } from "path";
2888
+ import { homedir as homedir5 } from "os";
3003
2889
  var READ_ONLY = /* @__PURE__ */ new Set([
3004
2890
  "read_file",
3005
2891
  "list_directory",
@@ -3040,6 +2926,40 @@ function permissionRules() {
3040
2926
  }
3041
2927
  return cachedRules;
3042
2928
  }
2929
+ function reloadPermissionRules() {
2930
+ cachedRules = null;
2931
+ }
2932
+ function saveGlobalPermissionRule(rule) {
2933
+ const { HW_DIR: HW_DIR2 } = getHwDir();
2934
+ const file = join7(HW_DIR2, "permissions.json");
2935
+ let rules = [];
2936
+ try {
2937
+ if (existsSync7(file)) {
2938
+ const parsed = JSON.parse(readFileSync7(file, "utf8"));
2939
+ const arr = Array.isArray(parsed) ? parsed : Array.isArray(parsed?.rules) ? parsed.rules : [];
2940
+ rules = arr.filter((r) => !!r && typeof r.tool === "string" && ["allow", "ask", "deny"].includes(r.effect));
2941
+ }
2942
+ } catch {
2943
+ rules = [];
2944
+ }
2945
+ const exists = rules.some((r) => r.tool === rule.tool && (r.match ?? "") === (rule.match ?? "") && r.effect === rule.effect);
2946
+ if (!exists) rules.push(rule);
2947
+ try {
2948
+ if (!existsSync7(HW_DIR2)) mkdirSync5(HW_DIR2, { recursive: true, mode: 448 });
2949
+ const tmp = join7(HW_DIR2, `permissions.json.tmp-${process.pid}`);
2950
+ writeFileSync5(tmp, JSON.stringify(rules, null, 2) + "\n", { mode: 384 });
2951
+ renameSync2(tmp, file);
2952
+ try {
2953
+ chmodSync2(file, 384);
2954
+ } catch {
2955
+ }
2956
+ } catch {
2957
+ }
2958
+ reloadPermissionRules();
2959
+ }
2960
+ function getHwDir() {
2961
+ return { HW_DIR: join7(homedir5(), ".hostwares") };
2962
+ }
3043
2963
  function isProjectTrusted(cwd = process.cwd()) {
3044
2964
  return (getConfig().trustedProjects ?? []).includes(cwd);
3045
2965
  }
@@ -3084,15 +3004,11 @@ ${c.yellow(glyph.caret)} ${describe(tool)}
3084
3004
  const toolPath = String(tool.input.path ?? tool.input.localPath ?? "");
3085
3005
  const rel = toolPath ? shortRel(toolPath) : "";
3086
3006
  const dir = rel ? rel.split("/").slice(0, -1).join("/") || "." : "";
3087
- const scopeOpts = [
3088
- `Specific paths \u2192 ${rel || tool.name} ${c.dim("(this file only)")}`,
3089
- `Complete directory \u2192 ${dir || "."} ${c.dim("(all files here)")}`,
3090
- `Entire Tool \u2192 * ${c.dim(`(always allow '${tool.name}')`)}`
3091
- ];
3092
3007
  const displayOpts = [
3093
3008
  `Specific paths \u2192 ${rel || tool.name}`,
3094
3009
  `Complete directory \u2192 ${dir || "."}`,
3095
- `Entire Tool \u2192 *`
3010
+ `Entire Tool \u2192 * (this session)`,
3011
+ `Always, everywhere \u2192 ${tool.name} (never ask again \u2014 saved)`
3096
3012
  ];
3097
3013
  const choice = await pickScope(displayOpts);
3098
3014
  if (choice === -1) return "abort";
@@ -3106,9 +3022,14 @@ ${c.yellow(glyph.caret)} ${describe(tool)}
3106
3022
  addSessionRule({ tool: tool.name, match, effect: "allow" });
3107
3023
  process.stdout.write(` ${c.dim(`Trusted '${tool.name}' for ${match} this session.`)}
3108
3024
  `);
3109
- } else {
3025
+ } else if (choice === 2) {
3110
3026
  addSessionRule({ tool: tool.name, match: "*", effect: "allow" });
3111
3027
  process.stdout.write(` ${c.dim(`Trusted '${tool.name}' (*) this session.`)}
3028
+ `);
3029
+ } else {
3030
+ addSessionRule({ tool: tool.name, match: "*", effect: "allow" });
3031
+ saveGlobalPermissionRule({ tool: tool.name, match: "*", effect: "allow" });
3032
+ process.stdout.write(` ${c.green(glyph.tick)} ${c.dim(`Always allowing '${tool.name}' everywhere \u2014 saved to ~/.hostwares/permissions.json. You won't be asked again.`)}
3112
3033
  `);
3113
3034
  }
3114
3035
  return "allow";
@@ -3406,13 +3327,13 @@ init_config();
3406
3327
  // src/session/store.ts
3407
3328
  init_config();
3408
3329
  import { createHash } from "crypto";
3409
- import { join as join7 } from "path";
3410
- import { existsSync as existsSync7, readFileSync as readFileSync7, readdirSync as readdirSync5, unlinkSync as unlinkSync2, mkdirSync as mkdirSync5 } from "fs";
3411
- var SESSION_DIR = join7(HW_DIR, "sessions");
3330
+ import { join as join8 } from "path";
3331
+ import { existsSync as existsSync8, readFileSync as readFileSync8, readdirSync as readdirSync5, unlinkSync as unlinkSync2, mkdirSync as mkdirSync6 } from "fs";
3332
+ var SESSION_DIR = join8(HW_DIR, "sessions");
3412
3333
  var SCHEMA_VERSION = 2;
3413
3334
  function sessionPath(cwd) {
3414
3335
  const hash = createHash("sha256").update(cwd).digest("hex").slice(0, 24);
3415
- return join7(SESSION_DIR, `${hash}.json`);
3336
+ return join8(SESSION_DIR, `${hash}.json`);
3416
3337
  }
3417
3338
  function newSession(cwd = process.cwd()) {
3418
3339
  const now = (/* @__PURE__ */ new Date()).toISOString();
@@ -3428,9 +3349,9 @@ function newSession(cwd = process.cwd()) {
3428
3349
  }
3429
3350
  function loadSession(cwd = process.cwd()) {
3430
3351
  const file = sessionPath(cwd);
3431
- if (!existsSync7(file)) return null;
3352
+ if (!existsSync8(file)) return null;
3432
3353
  try {
3433
- const data = JSON.parse(readFileSync7(file, "utf8"));
3354
+ const data = JSON.parse(readFileSync8(file, "utf8"));
3434
3355
  if (data.version !== SCHEMA_VERSION) return null;
3435
3356
  if (data.cwd !== cwd) return null;
3436
3357
  if (!Array.isArray(data.turns)) return null;
@@ -3452,7 +3373,7 @@ function isTurn(t) {
3452
3373
  }
3453
3374
  var MAX_LOCAL_TURNS = 200;
3454
3375
  function saveSession(session) {
3455
- mkdirSync5(SESSION_DIR, { recursive: true, mode: 448 });
3376
+ mkdirSync6(SESSION_DIR, { recursive: true, mode: 448 });
3456
3377
  session.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
3457
3378
  if (session.turns.length > MAX_LOCAL_TURNS) {
3458
3379
  session.turns = session.turns.slice(-MAX_LOCAL_TURNS);
@@ -3463,12 +3384,12 @@ function appendTurn(session, role, content) {
3463
3384
  session.turns.push({ role, content, at: (/* @__PURE__ */ new Date()).toISOString() });
3464
3385
  }
3465
3386
  function listSessions() {
3466
- if (!existsSync7(SESSION_DIR)) return [];
3387
+ if (!existsSync8(SESSION_DIR)) return [];
3467
3388
  const out = [];
3468
3389
  for (const name of readdirSync5(SESSION_DIR)) {
3469
3390
  if (!name.endsWith(".json")) continue;
3470
3391
  try {
3471
- const data = JSON.parse(readFileSync7(join7(SESSION_DIR, name), "utf8"));
3392
+ const data = JSON.parse(readFileSync8(join8(SESSION_DIR, name), "utf8"));
3472
3393
  if (data.version !== SCHEMA_VERSION || !data.cwd) continue;
3473
3394
  out.push({
3474
3395
  cwd: data.cwd,
@@ -4011,7 +3932,7 @@ async function listSites() {
4011
3932
  function readVersion() {
4012
3933
  try {
4013
3934
  const here = dirname4(fileURLToPath(import.meta.url));
4014
- const pkg = JSON.parse(readFileSync8(join8(here, "..", "package.json"), "utf8"));
3935
+ const pkg = JSON.parse(readFileSync9(join9(here, "..", "package.json"), "utf8"));
4015
3936
  return pkg.version ?? "0.0.0";
4016
3937
  } catch {
4017
3938
  return "0.0.0";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hostwares-cli",
3
- "version": "2.4.3",
3
+ "version": "2.4.5",
4
4
  "description": "Hostwares CLI - AI DevOps in your terminal",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",