hostwares-cli 2.4.4 → 2.4.6
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/dist/index.js +83 -27
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1864,6 +1864,7 @@ var init_safety = __esm({
|
|
|
1864
1864
|
});
|
|
1865
1865
|
|
|
1866
1866
|
// node_modules/@hostwares/agent-client/dist/esm/agent/loop.js
|
|
1867
|
+
import { readdirSync as readdirSync5 } from "fs";
|
|
1867
1868
|
async function runTurn(opts) {
|
|
1868
1869
|
const { credentials, signal, host } = opts;
|
|
1869
1870
|
const emit = host.onActivity ?? silentSink;
|
|
@@ -1878,7 +1879,9 @@ async function runTurn(opts) {
|
|
|
1878
1879
|
let toolResults = [];
|
|
1879
1880
|
let firstHop = true;
|
|
1880
1881
|
const steering = buildSteeringContext(host.cwd ?? process.cwd(), opts.message);
|
|
1881
|
-
const
|
|
1882
|
+
const cwdGrounding = buildCwdGrounding(host.cwd ?? process.cwd());
|
|
1883
|
+
const preamble = [steering, cwdGrounding].filter(Boolean).join("\n\n");
|
|
1884
|
+
const firstMessage = preamble ? `${preamble}
|
|
1882
1885
|
|
|
1883
1886
|
---
|
|
1884
1887
|
|
|
@@ -2031,14 +2034,14 @@ async function executePending(calls, opts, emit, checkpoint) {
|
|
|
2031
2034
|
let oldContent = null;
|
|
2032
2035
|
if (WRITE_TOOLS.has(call.name) && typeof call.input?.path === "string" && call.input.path) {
|
|
2033
2036
|
try {
|
|
2034
|
-
const { readFileSync:
|
|
2035
|
-
const { resolve: pathResolve2, join:
|
|
2036
|
-
const { homedir:
|
|
2037
|
+
const { readFileSync: readFileSync10, existsSync: existsSync9 } = await import("fs");
|
|
2038
|
+
const { resolve: pathResolve2, join: join10 } = await import("path");
|
|
2039
|
+
const { homedir: homedir6 } = await import("os");
|
|
2037
2040
|
const raw = String(call.input.path);
|
|
2038
|
-
const expanded = raw === "~" ?
|
|
2041
|
+
const expanded = raw === "~" ? homedir6() : raw.startsWith("~/") ? join10(homedir6(), raw.slice(2)) : raw;
|
|
2039
2042
|
const abs = pathResolve2(expanded);
|
|
2040
|
-
if (
|
|
2041
|
-
oldContent =
|
|
2043
|
+
if (existsSync9(abs))
|
|
2044
|
+
oldContent = readFileSync10(abs, "utf8");
|
|
2042
2045
|
else
|
|
2043
2046
|
oldContent = "";
|
|
2044
2047
|
} catch {
|
|
@@ -2135,6 +2138,21 @@ function byteLength(content) {
|
|
|
2135
2138
|
return void 0;
|
|
2136
2139
|
return Buffer.byteLength(content, "utf8");
|
|
2137
2140
|
}
|
|
2141
|
+
function buildCwdGrounding(cwd) {
|
|
2142
|
+
try {
|
|
2143
|
+
const SKIP = /* @__PURE__ */ new Set(["node_modules", ".git", ".next", "dist", "build", ".cache", ".DS_Store"]);
|
|
2144
|
+
const entries = readdirSync5(cwd, { withFileTypes: true }).filter((e) => !SKIP.has(e.name) && !e.name.startsWith(".")).slice(0, 40).map((e) => e.isDirectory() ? `${e.name}/` : e.name);
|
|
2145
|
+
const listing = entries.length ? entries.map((e) => `- ${e}`).join("\n") : "(empty)";
|
|
2146
|
+
return `## Working directory
|
|
2147
|
+
You are in: ${cwd}
|
|
2148
|
+
It contains:
|
|
2149
|
+
${listing}
|
|
2150
|
+
|
|
2151
|
+
When I say "it", "this", "the app/file", or ask to change/fix/edit something without naming a path, I mean the files in THIS directory \u2014 act on them directly, don't search elsewhere or use a remembered path. If the request is genuinely ambiguous and nothing here makes the target obvious, ask ONE short question instead of calling tools to guess.`;
|
|
2152
|
+
} catch {
|
|
2153
|
+
return "";
|
|
2154
|
+
}
|
|
2155
|
+
}
|
|
2138
2156
|
function targetOf(call) {
|
|
2139
2157
|
const i = call.input ?? {};
|
|
2140
2158
|
const raw = i.command ?? i.path ?? i.url ?? i.pattern ?? i.name ?? i.container ?? i.host ?? i.files;
|
|
@@ -2721,9 +2739,9 @@ var init_device = __esm({
|
|
|
2721
2739
|
});
|
|
2722
2740
|
|
|
2723
2741
|
// src/index.ts
|
|
2724
|
-
import { readFileSync as
|
|
2742
|
+
import { readFileSync as readFileSync9 } from "fs";
|
|
2725
2743
|
import { fileURLToPath } from "url";
|
|
2726
|
-
import { dirname as dirname4, join as
|
|
2744
|
+
import { dirname as dirname4, join as join9 } from "path";
|
|
2727
2745
|
|
|
2728
2746
|
// src/agent/run.ts
|
|
2729
2747
|
init_esm();
|
|
@@ -2883,6 +2901,9 @@ async function pickScope(options) {
|
|
|
2883
2901
|
init_theme();
|
|
2884
2902
|
init_render();
|
|
2885
2903
|
init_esm();
|
|
2904
|
+
import { existsSync as existsSync7, readFileSync as readFileSync7, writeFileSync as writeFileSync5, mkdirSync as mkdirSync5, renameSync as renameSync2, chmodSync as chmodSync2 } from "fs";
|
|
2905
|
+
import { join as join7 } from "path";
|
|
2906
|
+
import { homedir as homedir5 } from "os";
|
|
2886
2907
|
var READ_ONLY = /* @__PURE__ */ new Set([
|
|
2887
2908
|
"read_file",
|
|
2888
2909
|
"list_directory",
|
|
@@ -2923,6 +2944,40 @@ function permissionRules() {
|
|
|
2923
2944
|
}
|
|
2924
2945
|
return cachedRules;
|
|
2925
2946
|
}
|
|
2947
|
+
function reloadPermissionRules() {
|
|
2948
|
+
cachedRules = null;
|
|
2949
|
+
}
|
|
2950
|
+
function saveGlobalPermissionRule(rule) {
|
|
2951
|
+
const { HW_DIR: HW_DIR2 } = getHwDir();
|
|
2952
|
+
const file = join7(HW_DIR2, "permissions.json");
|
|
2953
|
+
let rules = [];
|
|
2954
|
+
try {
|
|
2955
|
+
if (existsSync7(file)) {
|
|
2956
|
+
const parsed = JSON.parse(readFileSync7(file, "utf8"));
|
|
2957
|
+
const arr = Array.isArray(parsed) ? parsed : Array.isArray(parsed?.rules) ? parsed.rules : [];
|
|
2958
|
+
rules = arr.filter((r) => !!r && typeof r.tool === "string" && ["allow", "ask", "deny"].includes(r.effect));
|
|
2959
|
+
}
|
|
2960
|
+
} catch {
|
|
2961
|
+
rules = [];
|
|
2962
|
+
}
|
|
2963
|
+
const exists = rules.some((r) => r.tool === rule.tool && (r.match ?? "") === (rule.match ?? "") && r.effect === rule.effect);
|
|
2964
|
+
if (!exists) rules.push(rule);
|
|
2965
|
+
try {
|
|
2966
|
+
if (!existsSync7(HW_DIR2)) mkdirSync5(HW_DIR2, { recursive: true, mode: 448 });
|
|
2967
|
+
const tmp = join7(HW_DIR2, `permissions.json.tmp-${process.pid}`);
|
|
2968
|
+
writeFileSync5(tmp, JSON.stringify(rules, null, 2) + "\n", { mode: 384 });
|
|
2969
|
+
renameSync2(tmp, file);
|
|
2970
|
+
try {
|
|
2971
|
+
chmodSync2(file, 384);
|
|
2972
|
+
} catch {
|
|
2973
|
+
}
|
|
2974
|
+
} catch {
|
|
2975
|
+
}
|
|
2976
|
+
reloadPermissionRules();
|
|
2977
|
+
}
|
|
2978
|
+
function getHwDir() {
|
|
2979
|
+
return { HW_DIR: join7(homedir5(), ".hostwares") };
|
|
2980
|
+
}
|
|
2926
2981
|
function isProjectTrusted(cwd = process.cwd()) {
|
|
2927
2982
|
return (getConfig().trustedProjects ?? []).includes(cwd);
|
|
2928
2983
|
}
|
|
@@ -2967,15 +3022,11 @@ ${c.yellow(glyph.caret)} ${describe(tool)}
|
|
|
2967
3022
|
const toolPath = String(tool.input.path ?? tool.input.localPath ?? "");
|
|
2968
3023
|
const rel = toolPath ? shortRel(toolPath) : "";
|
|
2969
3024
|
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
3025
|
const displayOpts = [
|
|
2976
3026
|
`Specific paths \u2192 ${rel || tool.name}`,
|
|
2977
3027
|
`Complete directory \u2192 ${dir || "."}`,
|
|
2978
|
-
`Entire Tool \u2192
|
|
3028
|
+
`Entire Tool \u2192 * (this session)`,
|
|
3029
|
+
`Always, everywhere \u2192 ${tool.name} (never ask again \u2014 saved)`
|
|
2979
3030
|
];
|
|
2980
3031
|
const choice = await pickScope(displayOpts);
|
|
2981
3032
|
if (choice === -1) return "abort";
|
|
@@ -2989,9 +3040,14 @@ ${c.yellow(glyph.caret)} ${describe(tool)}
|
|
|
2989
3040
|
addSessionRule({ tool: tool.name, match, effect: "allow" });
|
|
2990
3041
|
process.stdout.write(` ${c.dim(`Trusted '${tool.name}' for ${match} this session.`)}
|
|
2991
3042
|
`);
|
|
2992
|
-
} else {
|
|
3043
|
+
} else if (choice === 2) {
|
|
2993
3044
|
addSessionRule({ tool: tool.name, match: "*", effect: "allow" });
|
|
2994
3045
|
process.stdout.write(` ${c.dim(`Trusted '${tool.name}' (*) this session.`)}
|
|
3046
|
+
`);
|
|
3047
|
+
} else {
|
|
3048
|
+
addSessionRule({ tool: tool.name, match: "*", effect: "allow" });
|
|
3049
|
+
saveGlobalPermissionRule({ tool: tool.name, match: "*", effect: "allow" });
|
|
3050
|
+
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
3051
|
`);
|
|
2996
3052
|
}
|
|
2997
3053
|
return "allow";
|
|
@@ -3289,13 +3345,13 @@ init_config();
|
|
|
3289
3345
|
// src/session/store.ts
|
|
3290
3346
|
init_config();
|
|
3291
3347
|
import { createHash } from "crypto";
|
|
3292
|
-
import { join as
|
|
3293
|
-
import { existsSync as
|
|
3294
|
-
var SESSION_DIR =
|
|
3348
|
+
import { join as join8 } from "path";
|
|
3349
|
+
import { existsSync as existsSync8, readFileSync as readFileSync8, readdirSync as readdirSync6, unlinkSync as unlinkSync2, mkdirSync as mkdirSync6 } from "fs";
|
|
3350
|
+
var SESSION_DIR = join8(HW_DIR, "sessions");
|
|
3295
3351
|
var SCHEMA_VERSION = 2;
|
|
3296
3352
|
function sessionPath(cwd) {
|
|
3297
3353
|
const hash = createHash("sha256").update(cwd).digest("hex").slice(0, 24);
|
|
3298
|
-
return
|
|
3354
|
+
return join8(SESSION_DIR, `${hash}.json`);
|
|
3299
3355
|
}
|
|
3300
3356
|
function newSession(cwd = process.cwd()) {
|
|
3301
3357
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -3311,9 +3367,9 @@ function newSession(cwd = process.cwd()) {
|
|
|
3311
3367
|
}
|
|
3312
3368
|
function loadSession(cwd = process.cwd()) {
|
|
3313
3369
|
const file = sessionPath(cwd);
|
|
3314
|
-
if (!
|
|
3370
|
+
if (!existsSync8(file)) return null;
|
|
3315
3371
|
try {
|
|
3316
|
-
const data = JSON.parse(
|
|
3372
|
+
const data = JSON.parse(readFileSync8(file, "utf8"));
|
|
3317
3373
|
if (data.version !== SCHEMA_VERSION) return null;
|
|
3318
3374
|
if (data.cwd !== cwd) return null;
|
|
3319
3375
|
if (!Array.isArray(data.turns)) return null;
|
|
@@ -3335,7 +3391,7 @@ function isTurn(t) {
|
|
|
3335
3391
|
}
|
|
3336
3392
|
var MAX_LOCAL_TURNS = 200;
|
|
3337
3393
|
function saveSession(session) {
|
|
3338
|
-
|
|
3394
|
+
mkdirSync6(SESSION_DIR, { recursive: true, mode: 448 });
|
|
3339
3395
|
session.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3340
3396
|
if (session.turns.length > MAX_LOCAL_TURNS) {
|
|
3341
3397
|
session.turns = session.turns.slice(-MAX_LOCAL_TURNS);
|
|
@@ -3346,12 +3402,12 @@ function appendTurn(session, role, content) {
|
|
|
3346
3402
|
session.turns.push({ role, content, at: (/* @__PURE__ */ new Date()).toISOString() });
|
|
3347
3403
|
}
|
|
3348
3404
|
function listSessions() {
|
|
3349
|
-
if (!
|
|
3405
|
+
if (!existsSync8(SESSION_DIR)) return [];
|
|
3350
3406
|
const out = [];
|
|
3351
|
-
for (const name of
|
|
3407
|
+
for (const name of readdirSync6(SESSION_DIR)) {
|
|
3352
3408
|
if (!name.endsWith(".json")) continue;
|
|
3353
3409
|
try {
|
|
3354
|
-
const data = JSON.parse(
|
|
3410
|
+
const data = JSON.parse(readFileSync8(join8(SESSION_DIR, name), "utf8"));
|
|
3355
3411
|
if (data.version !== SCHEMA_VERSION || !data.cwd) continue;
|
|
3356
3412
|
out.push({
|
|
3357
3413
|
cwd: data.cwd,
|
|
@@ -3894,7 +3950,7 @@ async function listSites() {
|
|
|
3894
3950
|
function readVersion() {
|
|
3895
3951
|
try {
|
|
3896
3952
|
const here = dirname4(fileURLToPath(import.meta.url));
|
|
3897
|
-
const pkg = JSON.parse(
|
|
3953
|
+
const pkg = JSON.parse(readFileSync9(join9(here, "..", "package.json"), "utf8"));
|
|
3898
3954
|
return pkg.version ?? "0.0.0";
|
|
3899
3955
|
} catch {
|
|
3900
3956
|
return "0.0.0";
|