hostwares-cli 2.4.4 → 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 +63 -25
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -2031,14 +2031,14 @@ async function executePending(calls, opts, emit, checkpoint) {
2031
2031
  let oldContent = null;
2032
2032
  if (WRITE_TOOLS.has(call.name) && typeof call.input?.path === "string" && call.input.path) {
2033
2033
  try {
2034
- const { readFileSync: readFileSync9, existsSync: existsSync8 } = await import("fs");
2035
- const { resolve: pathResolve2, join: join9 } = await import("path");
2036
- 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");
2037
2037
  const raw = String(call.input.path);
2038
- 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;
2039
2039
  const abs = pathResolve2(expanded);
2040
- if (existsSync8(abs))
2041
- oldContent = readFileSync9(abs, "utf8");
2040
+ if (existsSync9(abs))
2041
+ oldContent = readFileSync10(abs, "utf8");
2042
2042
  else
2043
2043
  oldContent = "";
2044
2044
  } catch {
@@ -2721,9 +2721,9 @@ var init_device = __esm({
2721
2721
  });
2722
2722
 
2723
2723
  // src/index.ts
2724
- import { readFileSync as readFileSync8 } from "fs";
2724
+ import { readFileSync as readFileSync9 } from "fs";
2725
2725
  import { fileURLToPath } from "url";
2726
- import { dirname as dirname4, join as join8 } from "path";
2726
+ import { dirname as dirname4, join as join9 } from "path";
2727
2727
 
2728
2728
  // src/agent/run.ts
2729
2729
  init_esm();
@@ -2883,6 +2883,9 @@ async function pickScope(options) {
2883
2883
  init_theme();
2884
2884
  init_render();
2885
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";
2886
2889
  var READ_ONLY = /* @__PURE__ */ new Set([
2887
2890
  "read_file",
2888
2891
  "list_directory",
@@ -2923,6 +2926,40 @@ function permissionRules() {
2923
2926
  }
2924
2927
  return cachedRules;
2925
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
+ }
2926
2963
  function isProjectTrusted(cwd = process.cwd()) {
2927
2964
  return (getConfig().trustedProjects ?? []).includes(cwd);
2928
2965
  }
@@ -2967,15 +3004,11 @@ ${c.yellow(glyph.caret)} ${describe(tool)}
2967
3004
  const toolPath = String(tool.input.path ?? tool.input.localPath ?? "");
2968
3005
  const rel = toolPath ? shortRel(toolPath) : "";
2969
3006
  const dir = rel ? rel.split("/").slice(0, -1).join("/") || "." : "";
2970
- const scopeOpts = [
2971
- `Specific paths \u2192 ${rel || tool.name} ${c.dim("(this file only)")}`,
2972
- `Complete directory \u2192 ${dir || "."} ${c.dim("(all files here)")}`,
2973
- `Entire Tool \u2192 * ${c.dim(`(always allow '${tool.name}')`)}`
2974
- ];
2975
3007
  const displayOpts = [
2976
3008
  `Specific paths \u2192 ${rel || tool.name}`,
2977
3009
  `Complete directory \u2192 ${dir || "."}`,
2978
- `Entire Tool \u2192 *`
3010
+ `Entire Tool \u2192 * (this session)`,
3011
+ `Always, everywhere \u2192 ${tool.name} (never ask again \u2014 saved)`
2979
3012
  ];
2980
3013
  const choice = await pickScope(displayOpts);
2981
3014
  if (choice === -1) return "abort";
@@ -2989,9 +3022,14 @@ ${c.yellow(glyph.caret)} ${describe(tool)}
2989
3022
  addSessionRule({ tool: tool.name, match, effect: "allow" });
2990
3023
  process.stdout.write(` ${c.dim(`Trusted '${tool.name}' for ${match} this session.`)}
2991
3024
  `);
2992
- } else {
3025
+ } else if (choice === 2) {
2993
3026
  addSessionRule({ tool: tool.name, match: "*", effect: "allow" });
2994
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.`)}
2995
3033
  `);
2996
3034
  }
2997
3035
  return "allow";
@@ -3289,13 +3327,13 @@ init_config();
3289
3327
  // src/session/store.ts
3290
3328
  init_config();
3291
3329
  import { createHash } from "crypto";
3292
- import { join as join7 } from "path";
3293
- import { existsSync as existsSync7, readFileSync as readFileSync7, readdirSync as readdirSync5, unlinkSync as unlinkSync2, mkdirSync as mkdirSync5 } from "fs";
3294
- 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");
3295
3333
  var SCHEMA_VERSION = 2;
3296
3334
  function sessionPath(cwd) {
3297
3335
  const hash = createHash("sha256").update(cwd).digest("hex").slice(0, 24);
3298
- return join7(SESSION_DIR, `${hash}.json`);
3336
+ return join8(SESSION_DIR, `${hash}.json`);
3299
3337
  }
3300
3338
  function newSession(cwd = process.cwd()) {
3301
3339
  const now = (/* @__PURE__ */ new Date()).toISOString();
@@ -3311,9 +3349,9 @@ function newSession(cwd = process.cwd()) {
3311
3349
  }
3312
3350
  function loadSession(cwd = process.cwd()) {
3313
3351
  const file = sessionPath(cwd);
3314
- if (!existsSync7(file)) return null;
3352
+ if (!existsSync8(file)) return null;
3315
3353
  try {
3316
- const data = JSON.parse(readFileSync7(file, "utf8"));
3354
+ const data = JSON.parse(readFileSync8(file, "utf8"));
3317
3355
  if (data.version !== SCHEMA_VERSION) return null;
3318
3356
  if (data.cwd !== cwd) return null;
3319
3357
  if (!Array.isArray(data.turns)) return null;
@@ -3335,7 +3373,7 @@ function isTurn(t) {
3335
3373
  }
3336
3374
  var MAX_LOCAL_TURNS = 200;
3337
3375
  function saveSession(session) {
3338
- mkdirSync5(SESSION_DIR, { recursive: true, mode: 448 });
3376
+ mkdirSync6(SESSION_DIR, { recursive: true, mode: 448 });
3339
3377
  session.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
3340
3378
  if (session.turns.length > MAX_LOCAL_TURNS) {
3341
3379
  session.turns = session.turns.slice(-MAX_LOCAL_TURNS);
@@ -3346,12 +3384,12 @@ function appendTurn(session, role, content) {
3346
3384
  session.turns.push({ role, content, at: (/* @__PURE__ */ new Date()).toISOString() });
3347
3385
  }
3348
3386
  function listSessions() {
3349
- if (!existsSync7(SESSION_DIR)) return [];
3387
+ if (!existsSync8(SESSION_DIR)) return [];
3350
3388
  const out = [];
3351
3389
  for (const name of readdirSync5(SESSION_DIR)) {
3352
3390
  if (!name.endsWith(".json")) continue;
3353
3391
  try {
3354
- const data = JSON.parse(readFileSync7(join7(SESSION_DIR, name), "utf8"));
3392
+ const data = JSON.parse(readFileSync8(join8(SESSION_DIR, name), "utf8"));
3355
3393
  if (data.version !== SCHEMA_VERSION || !data.cwd) continue;
3356
3394
  out.push({
3357
3395
  cwd: data.cwd,
@@ -3894,7 +3932,7 @@ async function listSites() {
3894
3932
  function readVersion() {
3895
3933
  try {
3896
3934
  const here = dirname4(fileURLToPath(import.meta.url));
3897
- const pkg = JSON.parse(readFileSync8(join8(here, "..", "package.json"), "utf8"));
3935
+ const pkg = JSON.parse(readFileSync9(join9(here, "..", "package.json"), "utf8"));
3898
3936
  return pkg.version ?? "0.0.0";
3899
3937
  } catch {
3900
3938
  return "0.0.0";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hostwares-cli",
3
- "version": "2.4.4",
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",