@hasna/hooks 0.10.6 → 0.10.8

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.
@@ -1179,6 +1179,126 @@ var OUTPUT_REDIRECT = /(?:\d+|&)?(?:>>|>\||>&|<>|>)\s*([^\s;&|<>()\x60]*)/g;
1179
1179
  function withoutFdDuplication(segment) {
1180
1180
  return segment.replace(FD_DUPLICATION, " ");
1181
1181
  }
1182
+ function pipelineStages(segment) {
1183
+ const stages = [];
1184
+ let quote = null;
1185
+ let parens = 0;
1186
+ let braces = 0;
1187
+ let backtick = false;
1188
+ let start = 0;
1189
+ for (let i = 0;i < segment.length; i++) {
1190
+ const ch = segment[i];
1191
+ if (quote === "'") {
1192
+ if (ch === "'")
1193
+ quote = null;
1194
+ continue;
1195
+ }
1196
+ if (ch === "\\") {
1197
+ i++;
1198
+ continue;
1199
+ }
1200
+ if (ch === "(") {
1201
+ parens++;
1202
+ continue;
1203
+ }
1204
+ if (ch === ")") {
1205
+ if (--parens < 0)
1206
+ return [segment];
1207
+ continue;
1208
+ }
1209
+ if (ch === "$" && segment[i + 1] === "{") {
1210
+ braces++;
1211
+ i++;
1212
+ continue;
1213
+ }
1214
+ if (ch === "}" && braces > 0) {
1215
+ braces--;
1216
+ continue;
1217
+ }
1218
+ if (ch === "`") {
1219
+ backtick = !backtick;
1220
+ continue;
1221
+ }
1222
+ if (quote === '"') {
1223
+ if (ch === '"')
1224
+ quote = null;
1225
+ continue;
1226
+ }
1227
+ if (ch === "'" || ch === '"') {
1228
+ quote = ch;
1229
+ continue;
1230
+ }
1231
+ if (ch !== "|" || parens > 0 || braces > 0 || backtick || segment[i - 1] === ">")
1232
+ continue;
1233
+ stages.push(segment.slice(start, i));
1234
+ if (segment[i + 1] === "&")
1235
+ i++;
1236
+ start = i + 1;
1237
+ }
1238
+ if (quote || parens !== 0 || braces !== 0 || backtick)
1239
+ return [segment];
1240
+ stages.push(segment.slice(start));
1241
+ return stages;
1242
+ }
1243
+ function shellWords(command) {
1244
+ const words = [];
1245
+ let text = "";
1246
+ let raw = "";
1247
+ let quote = null;
1248
+ let inWord = false;
1249
+ const flush = () => {
1250
+ if (!inWord)
1251
+ return;
1252
+ const body = raw.replace(/^"?\$\{?HOME\}?"?(?=\/|$)/, "");
1253
+ const literalTilde = text.startsWith("~") && !/^~(?:\/|$)/.test(raw);
1254
+ words.push({ text: literalTilde ? `./${text}` : text, opaque: /[$\x60*?[]/.test(body) || /[()]/.test(body) });
1255
+ text = "";
1256
+ raw = "";
1257
+ inWord = false;
1258
+ };
1259
+ for (let i = 0;i < command.length; i++) {
1260
+ const ch = command[i];
1261
+ if (quote === "'") {
1262
+ if (ch === "'")
1263
+ quote = null;
1264
+ else
1265
+ text += ch;
1266
+ raw += ch;
1267
+ continue;
1268
+ }
1269
+ if (ch === "\\" && i + 1 < command.length) {
1270
+ const next = command[i + 1];
1271
+ text += quote === '"' && !/[$\x60"\\\n]/.test(next) ? ch + next : next;
1272
+ raw += ch + next;
1273
+ inWord = true;
1274
+ i++;
1275
+ continue;
1276
+ }
1277
+ if (quote === '"') {
1278
+ if (ch === '"')
1279
+ quote = null;
1280
+ else
1281
+ text += ch;
1282
+ raw += ch;
1283
+ continue;
1284
+ }
1285
+ if (/\s/.test(ch)) {
1286
+ flush();
1287
+ continue;
1288
+ }
1289
+ inWord = true;
1290
+ raw += ch;
1291
+ if (ch === "'" || ch === '"')
1292
+ quote = ch;
1293
+ else
1294
+ text += ch;
1295
+ }
1296
+ if (quote) {
1297
+ raw += "$";
1298
+ }
1299
+ flush();
1300
+ return words;
1301
+ }
1182
1302
  function segmentOperation(segment) {
1183
1303
  const trimmed = segment.trim();
1184
1304
  if (!trimmed)
@@ -1281,7 +1401,6 @@ function bashTargets(command, home, cwd) {
1281
1401
  for (let i = 0;i < opens; i++)
1282
1402
  subshells.push({ current: [...current], reachable: new Set(reachable), certain });
1283
1403
  const segment = unwrapSegment(rawSegment);
1284
- const op = segmentOperation(segment);
1285
1404
  const plainCd = segment.match(/^cd(?:\s+(?:-[A-Za-z@]+|--))*(?:\s+([^\s;&|<>()\x60]+))?$/);
1286
1405
  const embeddedCd = plainCd ? null : segment.match(/(?:^|\s)cd(?:\s+(?:-[A-Za-z]+|--))*\s+([^\s;&|<>()\x60]+)/);
1287
1406
  if (embeddedCd) {
@@ -1294,28 +1413,90 @@ function bashTargets(command, home, cwd) {
1294
1413
  const suffix = roots.map((root) => regexEscape(root.slice(home.length + 1))).join("|");
1295
1414
  const prefixRe = new RegExp(`(?:~|\\$HOME"*|\\$\\{HOME\\}"*|${homeLiteral}"*)/(?:${suffix})`);
1296
1415
  const re = new RegExp(`(${prefixRe.source})([^\\s"';&|<>()\`]*|$)`, "g");
1297
- let m;
1298
- let foundExplicit = false;
1299
- while ((m = re.exec(segment)) !== null) {
1300
- foundExplicit = true;
1301
- const expanded = expandHomeSpelling2(m[0], home);
1302
- targets.push({ path: normalize2(expanded).replace(/\/+$/, ""), op });
1303
- }
1304
- if (!foundExplicit && (op === "delete" || op === "write")) {
1416
+ const explicitTargets = (text) => {
1417
+ const found = [];
1418
+ let m;
1419
+ re.lastIndex = 0;
1420
+ while ((m = re.exec(text)) !== null)
1421
+ found.push(normalize2(expandHomeSpelling2(m[0], home)).replace(/\/+$/, ""));
1422
+ return found;
1423
+ };
1424
+ const stages = pipelineStages(segment).map((stage) => {
1305
1425
  const operands = [];
1306
- const commandPart = withoutFdDuplication(segment).replace(OUTPUT_REDIRECT, (_match, operand) => {
1426
+ let opaqueRedirect = false;
1427
+ const commandPart = withoutFdDuplication(stage).replace(OUTPUT_REDIRECT, (_match, operand) => {
1428
+ if (!operand || /[$\x60]/.test(operand))
1429
+ opaqueRedirect = true;
1307
1430
  if (operand)
1308
1431
  operands.push(operand);
1309
1432
  return " ";
1310
1433
  });
1311
1434
  const commandOp = segmentOperation(commandPart);
1312
- const relMatch = commandOp === "read" ? null : commandPart.match(REL_OPERAND);
1313
- for (const base of current.filter(underRoot)) {
1314
- for (const operand of operands) {
1435
+ return { commandPart, operands, op: opaqueRedirect && commandOp === "read" ? "write" : commandOp };
1436
+ });
1437
+ const pipelineOp = stages.some((stage) => stage.op === "delete") ? "delete" : stages.some((stage) => stage.op === "write") ? "write" : "read";
1438
+ const bases = current.filter(underRoot);
1439
+ for (const stage of stages) {
1440
+ let foundExplicit = false;
1441
+ for (const path of explicitTargets(stage.commandPart)) {
1442
+ foundExplicit = true;
1443
+ targets.push({ path, op: pipelineOp });
1444
+ }
1445
+ for (const operand of stage.operands) {
1446
+ const explicit = explicitTargets(operand);
1447
+ for (const path of explicit)
1448
+ targets.push({ path, op: "write" });
1449
+ if (explicit.length > 0)
1450
+ continue;
1451
+ for (const base of bases) {
1315
1452
  targets.push({ path: normalize2(resolve3(base, expandHomeSpelling2(operand, home))), op: "write" });
1316
1453
  }
1317
- if (relMatch)
1318
- targets.push({ path: normalize2(resolve3(base, relMatch[1])), op: commandOp });
1454
+ }
1455
+ if (foundExplicit || stage.op === "read")
1456
+ continue;
1457
+ const words = shellWords(stage.commandPart);
1458
+ let endOfOptions = false;
1459
+ const nonFlag = words.slice(1).flatMap((word) => {
1460
+ if (!endOfOptions && word.text === "--") {
1461
+ endOfOptions = true;
1462
+ return [];
1463
+ }
1464
+ if (!endOfOptions && word.text.startsWith("-"))
1465
+ return [];
1466
+ endOfOptions = true;
1467
+ return [word.opaque ? "." : word.text];
1468
+ });
1469
+ const everyOperand = stage.op === "delete" || /(?:^|\s)(?:mkdir|mkfile|touch|tee|truncate|install|mv)(?:\s|$)/.test(stage.commandPart) || /(?:^|\s)sed(?:\s|$)/.test(stage.commandPart) && /(?:^|\s)(?:-i\S*|--in-place\S*)(?:\s|$)/.test(stage.commandPart);
1470
+ const operandTargets = [];
1471
+ if (everyOperand) {
1472
+ for (const operand of nonFlag.length > 0 ? nonFlag : ["."])
1473
+ operandTargets.push({ operand, op: stage.op });
1474
+ } else {
1475
+ const relMatch = stage.commandPart.match(REL_OPERAND);
1476
+ const last = words.at(-1);
1477
+ operandTargets.push({ operand: relMatch ? relMatch[1] : !last || last.opaque ? "." : last.text, op: stage.op });
1478
+ if (/(?:^|\s)(?:cp|ln|install|mv)(?:\s|$)/.test(stage.commandPart)) {
1479
+ words.forEach((word, index2) => {
1480
+ let value;
1481
+ const short = word.text.match(/^-[A-Za-z]*?t(.*)$/);
1482
+ const long = word.text.match(/^(--t[a-z-]*)(?:=(.*))?$/);
1483
+ if (short && !word.text.startsWith("--")) {
1484
+ value = short[1] ? { text: short[1], opaque: word.opaque } : words[index2 + 1];
1485
+ } else if (long && "--target-directory".startsWith(long[1])) {
1486
+ value = long[2] !== undefined ? { text: long[2], opaque: word.opaque } : words[index2 + 1];
1487
+ }
1488
+ if (value)
1489
+ operandTargets.push({ operand: value.opaque || !value.text ? "." : value.text, op: stage.op });
1490
+ });
1491
+ }
1492
+ if (/(?:^|\s)(?:python3?|node|bun)\b[^;&|]*\s+-[ce](?:\s|$)/.test(stage.commandPart)) {
1493
+ operandTargets.push({ operand: ".", op: stage.op });
1494
+ }
1495
+ }
1496
+ for (const base of bases) {
1497
+ for (const { operand, op } of operandTargets) {
1498
+ targets.push({ path: normalize2(resolve3(base, expandHomeSpelling2(operand, home))), op });
1499
+ }
1319
1500
  }
1320
1501
  }
1321
1502
  if (plainCd) {
package/dist/index.d.ts CHANGED
@@ -94,3 +94,6 @@ export { handleServeRequest, startServeServer, DEFAULT_SERVE_PORT, } from "./ser
94
94
  export type { CatalogEntry, ArtifactPayload } from "./serve.js";
95
95
  export { provisionCloudflareResources, } from "./cf/provision.js";
96
96
  export type { ProvisionOptions, ProvisionResult } from "./cf/provision.js";
97
+ export { planCodexSafetyTrust, applyCodexSafetyTrust, CodexTrustError, type CodexTrustPlan } from "./lib/codex-safety-trust.js";
98
+ export { verifyClaudeSafetyConfiguration, ClaudeSafetyError, type ClaudeSafetyOptions } from "./lib/claude-safety-check.js";
99
+ export { planNativeSafetyRegistration, applyNativeSafetyRegistration, NativeRegistrationError, type NativeRegistrationPlan, type NativeRegistrationOptions } from "./lib/native-safety-registration.js";