@pushary/agent-hooks 0.22.0 → 0.24.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/data/cursor-plugin/README.md +5 -1
- package/data/cursor-plugin/mcp.json +1 -1
- package/dist/bin/pushary-clean.js +1 -1
- package/dist/bin/pushary-codex-hook.js +2 -4
- package/dist/bin/pushary-codex.js +1 -2
- package/dist/bin/pushary-doctor.js +2 -2
- package/dist/bin/pushary-gemini-hook.js +2 -4
- package/dist/bin/pushary-hook.js +2 -3
- package/dist/bin/pushary-post-hook.js +1 -2
- package/dist/bin/pushary-prompt-hook.js +1 -2
- package/dist/bin/pushary-setup.js +39 -3
- package/dist/bin/pushary-stop-hook.js +1 -2
- package/dist/{chunk-BHAMEPOP.js → chunk-JPRRYC5X.js} +5 -0
- package/dist/{chunk-5LTQNC7R.js → chunk-OQZH5PVA.js} +242 -33
- package/dist/{chunk-3LC4YUMA.js → chunk-U4SSWRVM.js} +1 -1
- package/dist/src/index.d.ts +15 -10
- package/dist/src/index.js +2 -3
- package/package.json +1 -1
- package/dist/chunk-22CV7V7A.js +0 -38
- package/dist/chunk-2BE2IPMO.js +0 -315
- package/dist/chunk-2CRLBMOX.js +0 -244
- package/dist/chunk-2HMNOZPY.js +0 -109
- package/dist/chunk-2I6DLXJN.js +0 -219
- package/dist/chunk-2Q6XPZ4Q.js +0 -395
- package/dist/chunk-3MIR7ODJ.js +0 -112
- package/dist/chunk-3QZESA66.js +0 -222
- package/dist/chunk-4Z4MB37G.js +0 -96
- package/dist/chunk-5GFUI5N6.js +0 -132
- package/dist/chunk-5JEDLXEC.js +0 -99
- package/dist/chunk-5QHQVXWN.js +0 -174
- package/dist/chunk-6A3WCJO2.js +0 -416
- package/dist/chunk-6QWFMVCD.js +0 -328
- package/dist/chunk-6SRXMZAN.js +0 -99
- package/dist/chunk-7PTU7TGE.js +0 -96
- package/dist/chunk-AB4KX4XT.js +0 -109
- package/dist/chunk-AC4UYAGX.js +0 -136
- package/dist/chunk-ACE77TKQ.js +0 -328
- package/dist/chunk-BYNYYZWW.js +0 -412
- package/dist/chunk-C5TFTNHG.js +0 -244
- package/dist/chunk-CH53PBQN.js +0 -265
- package/dist/chunk-DF3BM6BF.js +0 -195
- package/dist/chunk-DKZVKEOW.js +0 -175
- package/dist/chunk-EMPL27ZV.js +0 -96
- package/dist/chunk-EQJXMEIR.js +0 -175
- package/dist/chunk-FXYTRZHC.js +0 -177
- package/dist/chunk-GTJGZBTQ.js +0 -328
- package/dist/chunk-HJMFYDWY.js +0 -222
- package/dist/chunk-HRQEECB6.js +0 -315
- package/dist/chunk-HWEMAAOY.js +0 -370
- package/dist/chunk-IBWCHA5M.js +0 -10
- package/dist/chunk-KTM4ZQ3W.js +0 -96
- package/dist/chunk-KTP2EPVB.js +0 -27
- package/dist/chunk-KYARP7KP.js +0 -97
- package/dist/chunk-M2N5DYWN.js +0 -247
- package/dist/chunk-M5F2L4KF.js +0 -177
- package/dist/chunk-M6S3M6E3.js +0 -244
- package/dist/chunk-N7VXDBQU.js +0 -6
- package/dist/chunk-O6A5RHWY.js +0 -122
- package/dist/chunk-ODUXELPM.js +0 -219
- package/dist/chunk-OF5WIOYS.js +0 -129
- package/dist/chunk-OP4QAN23.js +0 -99
- package/dist/chunk-P4JH2Q7Z.js +0 -102
- package/dist/chunk-PMD5JSV3.js +0 -242
- package/dist/chunk-PSSHNBEL.js +0 -177
- package/dist/chunk-QEPRH7JB.js +0 -315
- package/dist/chunk-QRXWPZKN.js +0 -308
- package/dist/chunk-QY4L6XHN.js +0 -384
- package/dist/chunk-RNWPCELY.js +0 -176
- package/dist/chunk-RSHN2AQ7.js +0 -29
- package/dist/chunk-RZLVE57X.js +0 -38
- package/dist/chunk-SDCIKREA.js +0 -241
- package/dist/chunk-SDREQWNI.js +0 -175
- package/dist/chunk-SH26ZOHU.js +0 -159
- package/dist/chunk-TRLBBLSS.js +0 -176
- package/dist/chunk-U6OXZPYC.js +0 -384
- package/dist/chunk-UIBTFZUD.js +0 -314
- package/dist/chunk-USUCPCUC.js +0 -156
- package/dist/chunk-V4GLWPD7.js +0 -174
- package/dist/chunk-VC6U3QGF.js +0 -142
- package/dist/chunk-VIST7ACL.js +0 -88
- package/dist/chunk-VUNL35KE.js +0 -16
- package/dist/chunk-VWBNI4SC.js +0 -174
- package/dist/chunk-W5KRWUNE.js +0 -262
- package/dist/chunk-WCGKLHCL.js +0 -154
- package/dist/chunk-WNXGIEX7.js +0 -219
- package/dist/chunk-XPVSZIA3.js +0 -315
- package/dist/chunk-Y633ZIJF.js +0 -384
- package/dist/chunk-YCL4GUFR.js +0 -222
- package/dist/chunk-YTMKB44I.js +0 -220
- package/dist/chunk-ZWBS3T7Q.js +0 -244
- package/dist/pushary-mode-T7XOPI6Z.js +0 -83
|
@@ -48,7 +48,7 @@ Add this to `.cursor/mcp.json` in your project, or `~/.cursor/mcp.json` for ever
|
|
|
48
48
|
"pushary": {
|
|
49
49
|
"type": "http",
|
|
50
50
|
"url": "https://pushary.com/api/mcp/mcp",
|
|
51
|
-
"headers": { "Authorization": "Bearer ${PUSHARY_API_KEY}" }
|
|
51
|
+
"headers": { "Authorization": "Bearer ${env:PUSHARY_API_KEY}" }
|
|
52
52
|
}
|
|
53
53
|
}
|
|
54
54
|
}
|
|
@@ -67,6 +67,10 @@ source ~/.zshrc
|
|
|
67
67
|
|
|
68
68
|
Install the Pushary app on your phone (or turn on web push) so the agent can reach you.
|
|
69
69
|
|
|
70
|
+
## Troubleshooting
|
|
71
|
+
|
|
72
|
+
macOS GUI apps do not read `.zshrc`. If Settings > MCP shows pushary in red after you set the env var, launch Cursor from a terminal where `PUSHARY_API_KEY` is exported, or set the variable system-wide so GUI apps see it.
|
|
73
|
+
|
|
70
74
|
## What is in the plugin
|
|
71
75
|
|
|
72
76
|
| Part | File | What it does |
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
} from "../chunk-KQYIHZ5E.js";
|
|
6
6
|
import {
|
|
7
7
|
CODEX_AGENT,
|
|
8
|
+
DECISION_LINE_MAX,
|
|
8
9
|
DEFAULT_SESSION,
|
|
9
10
|
askUser,
|
|
10
11
|
codexAllow,
|
|
@@ -31,10 +32,7 @@ import {
|
|
|
31
32
|
toCodexWire,
|
|
32
33
|
toPolicyLookup,
|
|
33
34
|
waitForAnswer
|
|
34
|
-
} from "../chunk-
|
|
35
|
-
import {
|
|
36
|
-
DECISION_LINE_MAX
|
|
37
|
-
} from "../chunk-WPVL6VIT.js";
|
|
35
|
+
} from "../chunk-OQZH5PVA.js";
|
|
38
36
|
import "../chunk-DWED7BS3.js";
|
|
39
37
|
import {
|
|
40
38
|
getApiKey
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
missingCodexHookEvents,
|
|
9
9
|
missingGeminiHookEvents,
|
|
10
10
|
untrustedCodexHookEvents
|
|
11
|
-
} from "../chunk-
|
|
11
|
+
} from "../chunk-JPRRYC5X.js";
|
|
12
12
|
import {
|
|
13
13
|
callMcpTool,
|
|
14
14
|
sendMcpRequest
|
|
@@ -283,7 +283,7 @@ var main = async () => {
|
|
|
283
283
|
const cursorAuth = cursorServers.pushary?.headers?.Authorization;
|
|
284
284
|
if (!cursorAuth) {
|
|
285
285
|
check(false, "Cursor: API key linked", "no Authorization in plugin mcp.json \u2014 re-run setup");
|
|
286
|
-
} else if (cursorAuth.includes("
|
|
286
|
+
} else if (cursorAuth.includes("PUSHARY_API_KEY")) {
|
|
287
287
|
check(false, "Cursor: API key linked", "plugin relies on $PUSHARY_API_KEY; re-run setup to embed the key so the gate works when Cursor is launched from the GUI");
|
|
288
288
|
} else {
|
|
289
289
|
check(true, "Cursor: API key linked", "embedded in plugin mcp.json");
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
isDeferAnswer
|
|
5
5
|
} from "../chunk-KQYIHZ5E.js";
|
|
6
6
|
import {
|
|
7
|
+
DECISION_LINE_MAX,
|
|
7
8
|
DEFAULT_SESSION,
|
|
8
9
|
askUser,
|
|
9
10
|
deriveActionBody,
|
|
@@ -23,10 +24,7 @@ import {
|
|
|
23
24
|
savePendingQuestion,
|
|
24
25
|
sendNotification,
|
|
25
26
|
waitForAnswer
|
|
26
|
-
} from "../chunk-
|
|
27
|
-
import {
|
|
28
|
-
DECISION_LINE_MAX
|
|
29
|
-
} from "../chunk-WPVL6VIT.js";
|
|
27
|
+
} from "../chunk-OQZH5PVA.js";
|
|
30
28
|
import "../chunk-DWED7BS3.js";
|
|
31
29
|
import {
|
|
32
30
|
getApiKey
|
package/dist/bin/pushary-hook.js
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
handlePreToolUse
|
|
4
|
-
} from "../chunk-
|
|
4
|
+
} from "../chunk-U4SSWRVM.js";
|
|
5
5
|
import "../chunk-KQYIHZ5E.js";
|
|
6
|
-
import "../chunk-
|
|
7
|
-
import "../chunk-WPVL6VIT.js";
|
|
6
|
+
import "../chunk-OQZH5PVA.js";
|
|
8
7
|
import "../chunk-DWED7BS3.js";
|
|
9
8
|
import "../chunk-NKXSILEW.js";
|
|
10
9
|
|
|
@@ -13,11 +13,15 @@ import {
|
|
|
13
13
|
execNpm,
|
|
14
14
|
npmErrorMessage,
|
|
15
15
|
renderAgentInstructions,
|
|
16
|
+
renderProjectAgentInstructions,
|
|
16
17
|
writeInstructionBlock
|
|
17
|
-
} from "../chunk-
|
|
18
|
+
} from "../chunk-JPRRYC5X.js";
|
|
18
19
|
import {
|
|
19
|
-
isValidApiKey
|
|
20
|
-
|
|
20
|
+
isValidApiKey,
|
|
21
|
+
reportEvent
|
|
22
|
+
} from "../chunk-OQZH5PVA.js";
|
|
23
|
+
import "../chunk-DWED7BS3.js";
|
|
24
|
+
import "../chunk-NKXSILEW.js";
|
|
21
25
|
|
|
22
26
|
// bin/pushary-setup.ts
|
|
23
27
|
import { existsSync, readFileSync, writeFileSync, appendFileSync, mkdirSync, cpSync, rmSync, chmodSync } from "fs";
|
|
@@ -857,6 +861,26 @@ var AGENT_SETUP = {
|
|
|
857
861
|
cursor: setupCursor,
|
|
858
862
|
custom: setupCustom
|
|
859
863
|
};
|
|
864
|
+
var PROJECT_INSTRUCTION_TARGETS = {
|
|
865
|
+
claude_code: { file: "CLAUDE.md", label: "Claude Code" },
|
|
866
|
+
codex: { file: "AGENTS.md", label: "Codex" }
|
|
867
|
+
};
|
|
868
|
+
var offerProjectInstructions = async (agents) => {
|
|
869
|
+
const targets = agents.map((agent) => PROJECT_INSTRUCTION_TARGETS[agent]).filter((target) => target !== void 0);
|
|
870
|
+
if (targets.length === 0 || !process.stdout.isTTY) return;
|
|
871
|
+
console.log();
|
|
872
|
+
const wanted = await confirm({
|
|
873
|
+
message: "Add Pushary instructions to this project's CLAUDE.md/AGENTS.md so your whole team's agents can use phone approvals?",
|
|
874
|
+
default: false
|
|
875
|
+
});
|
|
876
|
+
if (!wanted) return;
|
|
877
|
+
for (const target of targets) {
|
|
878
|
+
await spinner(`Writing managed block to ${join(process.cwd(), target.file)}`, async () => {
|
|
879
|
+
writeInstructionBlock(join(process.cwd(), target.file), renderProjectAgentInstructions(target.label));
|
|
880
|
+
}, { optional: true });
|
|
881
|
+
}
|
|
882
|
+
console.log(` ${dim2("Commit the file to share it. Teammates without a key fall back to the terminal.")}`);
|
|
883
|
+
};
|
|
860
884
|
var main = async () => {
|
|
861
885
|
const version = getPackageVersion();
|
|
862
886
|
console.log();
|
|
@@ -939,9 +963,11 @@ var main = async () => {
|
|
|
939
963
|
${dim2("No agents selected. API key saved.")}`);
|
|
940
964
|
} else {
|
|
941
965
|
const failed = [];
|
|
966
|
+
const completed = [];
|
|
942
967
|
for (const agent of agents) {
|
|
943
968
|
try {
|
|
944
969
|
await AGENT_SETUP[agent](trimmedKey);
|
|
970
|
+
completed.push(agent);
|
|
945
971
|
} catch (err) {
|
|
946
972
|
failed.push(agent.replace("_", " "));
|
|
947
973
|
console.log(` ${yellow2("!")} ${agent.replace("_", " ")} setup failed: ${formatError(err)}`);
|
|
@@ -952,6 +978,16 @@ var main = async () => {
|
|
|
952
978
|
console.log();
|
|
953
979
|
console.log(` ${yellow2("!")} Failed: ${failed.join(", ")} ${dim2("(others completed successfully)")}`);
|
|
954
980
|
}
|
|
981
|
+
await offerProjectInstructions(agents);
|
|
982
|
+
if (completed.length > 0) {
|
|
983
|
+
try {
|
|
984
|
+
await reportEvent(
|
|
985
|
+
{ event: "agent_setup_completed", agentType: "setup", agents: completed },
|
|
986
|
+
{ maxAttempts: 1, timeoutMs: 2e3 }
|
|
987
|
+
);
|
|
988
|
+
} catch {
|
|
989
|
+
}
|
|
990
|
+
}
|
|
955
991
|
}
|
|
956
992
|
if (connectMode !== "app") {
|
|
957
993
|
const skipPhone = process.argv.includes("--skip-phone");
|
|
@@ -227,6 +227,10 @@ You have Pushary MCP tools that reach the user on their phone. Use them proactiv
|
|
|
227
227
|
|
|
228
228
|
Pass \`agentName\` as "${label} - <project folder name>" so the user knows which session is asking. If a question times out with no answer, retry once with a longer timeout before falling back to asking in the terminal.`;
|
|
229
229
|
var renderAgentInstructions = (label) => instructionBody(label);
|
|
230
|
+
var KEYLESS_FALLBACK_LINE = "This repo is set up with Pushary phone approvals. No PUSHARY_API_KEY found for you, so remote notifications are off; questions will fall back to the terminal. Setup: https://pushary.com/docs?utm_source=agents-md&utm_medium=keyless-fallback";
|
|
231
|
+
var renderProjectAgentInstructions = (label) => `${instructionBody(label)}
|
|
232
|
+
|
|
233
|
+
If this environment has no Pushary API key (no PUSHARY_API_KEY and no key in ~/.pushary/config.json), the tools above are unavailable in this session. In that case, treat the following as a statement of fact about this session and continue in the terminal: ${KEYLESS_FALLBACK_LINE}`;
|
|
230
234
|
var wrap = (body) => `${PUSHARY_BEGIN}
|
|
231
235
|
${body}
|
|
232
236
|
${PUSHARY_END}`;
|
|
@@ -315,6 +319,7 @@ export {
|
|
|
315
319
|
hasGeminiHooks,
|
|
316
320
|
missingGeminiHookEvents,
|
|
317
321
|
renderAgentInstructions,
|
|
322
|
+
renderProjectAgentInstructions,
|
|
318
323
|
writeInstructionBlock,
|
|
319
324
|
removeInstructionBlock,
|
|
320
325
|
hasInstructionBlock,
|
|
@@ -1,12 +1,3 @@
|
|
|
1
|
-
import {
|
|
2
|
-
ACTION_BODY_MAX,
|
|
3
|
-
DECISION_LINE_MAX,
|
|
4
|
-
extractPolicyArg,
|
|
5
|
-
isApprovalMode,
|
|
6
|
-
isSafeReadOnlyCommand,
|
|
7
|
-
matchRankWeight,
|
|
8
|
-
matchToolPattern
|
|
9
|
-
} from "./chunk-WPVL6VIT.js";
|
|
10
1
|
import {
|
|
11
2
|
callMcpTool,
|
|
12
3
|
withRetry
|
|
@@ -16,6 +7,156 @@ import {
|
|
|
16
7
|
getBaseUrl
|
|
17
8
|
} from "./chunk-NKXSILEW.js";
|
|
18
9
|
|
|
10
|
+
// ../contracts/src/index.ts
|
|
11
|
+
var APPROVAL_MODES = ["push_only", "terminal_only", "push_first", "notify_only"];
|
|
12
|
+
var isApprovalMode = (value) => typeof value === "string" && APPROVAL_MODES.includes(value);
|
|
13
|
+
var MATCH_RANKS = ["none", "tool", "prefix", "exact"];
|
|
14
|
+
var matchRankWeight = (rank) => MATCH_RANKS.indexOf(rank);
|
|
15
|
+
var matchToolPattern = (pattern, toolName, arg) => {
|
|
16
|
+
const open = pattern.indexOf("(");
|
|
17
|
+
if (open === -1 || !pattern.endsWith(")")) {
|
|
18
|
+
return pattern === toolName ? "tool" : "none";
|
|
19
|
+
}
|
|
20
|
+
if (pattern.slice(0, open) !== toolName || arg === void 0) return "none";
|
|
21
|
+
const inner = pattern.slice(open + 1, -1);
|
|
22
|
+
if (inner.endsWith(":*")) {
|
|
23
|
+
return arg.startsWith(inner.slice(0, -2)) ? "prefix" : "none";
|
|
24
|
+
}
|
|
25
|
+
return arg === inner ? "exact" : "none";
|
|
26
|
+
};
|
|
27
|
+
var POLICY_ARG_KEYS = {
|
|
28
|
+
Bash: "command",
|
|
29
|
+
Edit: "file_path",
|
|
30
|
+
Write: "file_path"
|
|
31
|
+
};
|
|
32
|
+
var extractPolicyArg = (toolName, toolInput) => {
|
|
33
|
+
const key = POLICY_ARG_KEYS[toolName];
|
|
34
|
+
if (!key) return void 0;
|
|
35
|
+
const value = toolInput[key];
|
|
36
|
+
return typeof value === "string" ? value : void 0;
|
|
37
|
+
};
|
|
38
|
+
var SAFE_SHELL_COMMANDS = /* @__PURE__ */ new Set([
|
|
39
|
+
"ls",
|
|
40
|
+
"pwd",
|
|
41
|
+
"cd",
|
|
42
|
+
"cat",
|
|
43
|
+
"head",
|
|
44
|
+
"tail",
|
|
45
|
+
"wc",
|
|
46
|
+
"echo",
|
|
47
|
+
"printf",
|
|
48
|
+
"which",
|
|
49
|
+
"type",
|
|
50
|
+
"whoami",
|
|
51
|
+
"id",
|
|
52
|
+
"uname",
|
|
53
|
+
"arch",
|
|
54
|
+
"printenv",
|
|
55
|
+
"locale",
|
|
56
|
+
"tty",
|
|
57
|
+
"dirname",
|
|
58
|
+
"basename",
|
|
59
|
+
"realpath",
|
|
60
|
+
"readlink",
|
|
61
|
+
"stat",
|
|
62
|
+
"cut",
|
|
63
|
+
"nl",
|
|
64
|
+
"tr",
|
|
65
|
+
"comm",
|
|
66
|
+
"diff",
|
|
67
|
+
"cmp",
|
|
68
|
+
"grep",
|
|
69
|
+
"egrep",
|
|
70
|
+
"fgrep",
|
|
71
|
+
"jq",
|
|
72
|
+
"cksum",
|
|
73
|
+
"md5sum",
|
|
74
|
+
"sha1sum",
|
|
75
|
+
"sha256sum",
|
|
76
|
+
"du",
|
|
77
|
+
"df",
|
|
78
|
+
"ps",
|
|
79
|
+
"true"
|
|
80
|
+
]);
|
|
81
|
+
var SAFE_GIT_SUBCOMMANDS = /* @__PURE__ */ new Set([
|
|
82
|
+
"status",
|
|
83
|
+
"log",
|
|
84
|
+
"diff",
|
|
85
|
+
"show",
|
|
86
|
+
"rev-parse",
|
|
87
|
+
"describe",
|
|
88
|
+
"blame",
|
|
89
|
+
"shortlog",
|
|
90
|
+
"ls-files",
|
|
91
|
+
"ls-tree",
|
|
92
|
+
"cat-file",
|
|
93
|
+
"whatchanged",
|
|
94
|
+
"rev-list",
|
|
95
|
+
"name-rev",
|
|
96
|
+
"for-each-ref",
|
|
97
|
+
"var",
|
|
98
|
+
"count-objects"
|
|
99
|
+
]);
|
|
100
|
+
var GIT_WRITE_FLAGS = (token) => token === "--output" || token.startsWith("--output=");
|
|
101
|
+
var UNSAFE_SHELL_CHARS = /[;&|<>(){}`\\\n\r]/;
|
|
102
|
+
var basenameOf = (token) => {
|
|
103
|
+
const slash = Math.max(token.lastIndexOf("/"), token.lastIndexOf("\\"));
|
|
104
|
+
return slash === -1 ? token : token.slice(slash + 1);
|
|
105
|
+
};
|
|
106
|
+
var tokenizeShellCommand = (command) => {
|
|
107
|
+
const tokens = [];
|
|
108
|
+
let current = "";
|
|
109
|
+
let quote = null;
|
|
110
|
+
let started = false;
|
|
111
|
+
for (const ch of command) {
|
|
112
|
+
if (quote) {
|
|
113
|
+
if (ch === quote) quote = null;
|
|
114
|
+
else current += ch;
|
|
115
|
+
started = true;
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
if (ch === '"' || ch === "'") {
|
|
119
|
+
quote = ch;
|
|
120
|
+
started = true;
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
if (ch === " " || ch === " ") {
|
|
124
|
+
if (started) {
|
|
125
|
+
tokens.push(current);
|
|
126
|
+
current = "";
|
|
127
|
+
started = false;
|
|
128
|
+
}
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
current += ch;
|
|
132
|
+
started = true;
|
|
133
|
+
}
|
|
134
|
+
if (quote) return null;
|
|
135
|
+
if (started) tokens.push(current);
|
|
136
|
+
return tokens;
|
|
137
|
+
};
|
|
138
|
+
var isSafeReadOnlyCommand = (command) => {
|
|
139
|
+
const trimmed = command.trim();
|
|
140
|
+
if (!trimmed || trimmed.length > 2e3) return false;
|
|
141
|
+
if (UNSAFE_SHELL_CHARS.test(trimmed)) return false;
|
|
142
|
+
const tokens = tokenizeShellCommand(trimmed);
|
|
143
|
+
if (!tokens || tokens.length === 0) return false;
|
|
144
|
+
const first = tokens[0];
|
|
145
|
+
if (/^[A-Za-z_][A-Za-z0-9_]*=/.test(first) || first.includes("$")) return false;
|
|
146
|
+
const exe = basenameOf(first);
|
|
147
|
+
if (exe === "git") {
|
|
148
|
+
const sub = tokens[1];
|
|
149
|
+
if (!sub || sub.startsWith("-")) return false;
|
|
150
|
+
if (!SAFE_GIT_SUBCOMMANDS.has(sub)) return false;
|
|
151
|
+
return !tokens.some(GIT_WRITE_FLAGS);
|
|
152
|
+
}
|
|
153
|
+
return SAFE_SHELL_COMMANDS.has(exe);
|
|
154
|
+
};
|
|
155
|
+
var API_KEY_PATTERN = /^pk_[a-f0-9]+\.[a-f0-9]+$/;
|
|
156
|
+
var isValidApiKey = (value) => API_KEY_PATTERN.test(value);
|
|
157
|
+
var ACTION_BODY_MAX = 4e3;
|
|
158
|
+
var DECISION_LINE_MAX = 500;
|
|
159
|
+
|
|
19
160
|
// src/validate.ts
|
|
20
161
|
var isPolicyConfig = (data) => {
|
|
21
162
|
if (!data || typeof data !== "object") return false;
|
|
@@ -132,6 +273,15 @@ var autoApprove = (tool) => ({
|
|
|
132
273
|
mode: "terminal_only",
|
|
133
274
|
pushFirstSeconds: 0
|
|
134
275
|
});
|
|
276
|
+
var resolveAutoResolveOrigin = (config, toolName, toolInput) => {
|
|
277
|
+
const arg = toolInput ? extractPolicyArg(toolName, toolInput) : void 0;
|
|
278
|
+
const match = findBestMatch(config.policies, toolName, arg);
|
|
279
|
+
const governedBySpecificRule = match?.rank === "exact" || match?.rank === "prefix";
|
|
280
|
+
if (!governedBySpecificRule && toolName === "Bash" && typeof arg === "string" && isSafeReadOnlyCommand(arg)) {
|
|
281
|
+
return "safe_readonly";
|
|
282
|
+
}
|
|
283
|
+
return "policy_timeout";
|
|
284
|
+
};
|
|
135
285
|
var resolvePolicy = (config, toolName, modeOverride, toolInput) => {
|
|
136
286
|
const arg = toolInput ? extractPolicyArg(toolName, toolInput) : void 0;
|
|
137
287
|
const match = findBestMatch(config.policies, toolName, arg);
|
|
@@ -305,6 +455,12 @@ var deriveBlocker = (mode, reason) => {
|
|
|
305
455
|
return text.slice(0, DECISION_LINE_MAX);
|
|
306
456
|
};
|
|
307
457
|
|
|
458
|
+
// src/identity.ts
|
|
459
|
+
import { createHash as createHash2 } from "crypto";
|
|
460
|
+
import { hostname } from "os";
|
|
461
|
+
var deriveMachineId = (host) => createHash2("sha256").update(host).digest("hex").slice(0, 8);
|
|
462
|
+
var getMachineId = () => deriveMachineId(hostname());
|
|
463
|
+
|
|
308
464
|
// src/pending.ts
|
|
309
465
|
import { join as join2 } from "path";
|
|
310
466
|
import { tmpdir as tmpdir2 } from "os";
|
|
@@ -351,12 +507,6 @@ var removePendingSession = (sessionId) => {
|
|
|
351
507
|
}
|
|
352
508
|
};
|
|
353
509
|
|
|
354
|
-
// src/identity.ts
|
|
355
|
-
import { createHash as createHash2 } from "crypto";
|
|
356
|
-
import { hostname } from "os";
|
|
357
|
-
var deriveMachineId = (host) => createHash2("sha256").update(host).digest("hex").slice(0, 8);
|
|
358
|
-
var getMachineId = () => deriveMachineId(hostname());
|
|
359
|
-
|
|
360
510
|
// src/usage.ts
|
|
361
511
|
import { closeSync, mkdirSync as mkdirSync2, openSync, readFileSync as readFileSync2, readSync, statSync as statSync2, writeFileSync as writeFileSync3 } from "fs";
|
|
362
512
|
import { join as join3 } from "path";
|
|
@@ -606,29 +756,48 @@ var permissionTimeoutDecision = (timeoutAction) => {
|
|
|
606
756
|
};
|
|
607
757
|
var preToolUseTimeoutDecision = (timeoutAction, denyReason = "No response within timeout") => timeoutAction === "deny" ? codexDeny(denyReason) : codexPass();
|
|
608
758
|
|
|
609
|
-
// src/
|
|
610
|
-
import {
|
|
611
|
-
import { createHash as createHash3 } from "crypto";
|
|
612
|
-
import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync3, statSync as statSync3, writeFileSync as writeFileSync4 } from "fs";
|
|
759
|
+
// src/throttle.ts
|
|
760
|
+
import { join as join4 } from "path";
|
|
613
761
|
import { tmpdir as tmpdir4 } from "os";
|
|
614
|
-
|
|
762
|
+
import { createHash as createHash3 } from "crypto";
|
|
763
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync3, statSync as statSync3, writeFileSync as writeFileSync4 } from "fs";
|
|
764
|
+
var THROTTLE_DIR = join4(tmpdir4(), "pushary-throttle");
|
|
765
|
+
var markerPath = (key) => join4(THROTTLE_DIR, createHash3("sha256").update(key).digest("hex").slice(0, 16));
|
|
766
|
+
var throttlePass = (key, windowMs) => {
|
|
767
|
+
try {
|
|
768
|
+
const path = markerPath(key);
|
|
769
|
+
if (existsSync3(path) && Date.now() - statSync3(path).mtimeMs < windowMs) return false;
|
|
770
|
+
mkdirSync3(THROTTLE_DIR, { recursive: true });
|
|
771
|
+
writeFileSync4(path, "", "utf-8");
|
|
772
|
+
return true;
|
|
773
|
+
} catch {
|
|
774
|
+
return false;
|
|
775
|
+
}
|
|
776
|
+
};
|
|
777
|
+
|
|
778
|
+
// src/events.ts
|
|
779
|
+
import { basename, join as join5 } from "path";
|
|
780
|
+
import { createHash as createHash4 } from "crypto";
|
|
781
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync4, readFileSync as readFileSync3, statSync as statSync4, writeFileSync as writeFileSync5 } from "fs";
|
|
782
|
+
import { tmpdir as tmpdir5 } from "os";
|
|
783
|
+
var INTENT_DIR = join5(tmpdir5(), "pushary-intent");
|
|
615
784
|
var INTENT_MAX_BYTES = 2048;
|
|
616
785
|
var INTENT_GRACE_MS = 10 * 60 * 1e3;
|
|
617
786
|
var sanitizeSession = (sessionId) => sessionId.replace(/[^A-Za-z0-9_-]/g, "_").slice(0, 128) || DEFAULT_SESSION;
|
|
618
|
-
var intentFile = (sessionId) =>
|
|
787
|
+
var intentFile = (sessionId) => join5(INTENT_DIR, sanitizeSession(sessionId));
|
|
619
788
|
var saveLastPrompt = (sessionId, prompt) => {
|
|
620
789
|
try {
|
|
621
790
|
const trimmed = prompt.replace(/\s+/g, " ").trim().slice(0, INTENT_MAX_BYTES);
|
|
622
791
|
if (!trimmed) return;
|
|
623
|
-
|
|
624
|
-
|
|
792
|
+
mkdirSync4(INTENT_DIR, { recursive: true });
|
|
793
|
+
writeFileSync5(intentFile(sessionId), trimmed, "utf-8");
|
|
625
794
|
} catch {
|
|
626
795
|
}
|
|
627
796
|
};
|
|
628
797
|
var readLastPrompt = (sessionId) => {
|
|
629
798
|
try {
|
|
630
799
|
const path = intentFile(sessionId);
|
|
631
|
-
if (isDefaultSession(sessionId) &&
|
|
800
|
+
if (isDefaultSession(sessionId) && statSync4(path).mtimeMs < Date.now() - INTENT_GRACE_MS) return void 0;
|
|
632
801
|
const value = readFileSync3(path, "utf-8").trim();
|
|
633
802
|
return value.length > 0 ? value : void 0;
|
|
634
803
|
} catch {
|
|
@@ -653,9 +822,9 @@ var cleanupPendingQuestions = async (sessionId) => {
|
|
|
653
822
|
var CLAUDE_CODE_AGENT = { type: "claude_code", label: "Claude Code" };
|
|
654
823
|
var POLICY_CACHE_TTL_MS = 5 * 60 * 1e3;
|
|
655
824
|
var readFreshCachedPolicy = (apiKey) => {
|
|
656
|
-
const hash =
|
|
657
|
-
const path =
|
|
658
|
-
if (!
|
|
825
|
+
const hash = createHash4("sha256").update(apiKey).digest("hex").slice(0, 12);
|
|
826
|
+
const path = join5(tmpdir5(), `pushary-policy-${hash}.json`);
|
|
827
|
+
if (!existsSync4(path)) return null;
|
|
659
828
|
const cached = JSON.parse(readFileSync3(path, "utf-8"));
|
|
660
829
|
if (!isPolicyConfig(cached)) return null;
|
|
661
830
|
if (!cached._cachedAt || Date.now() - cached._cachedAt >= POLICY_CACHE_TTL_MS) return null;
|
|
@@ -674,6 +843,24 @@ var deriveDecisionSource = (toolName, toolInput, liveMode) => {
|
|
|
674
843
|
return void 0;
|
|
675
844
|
}
|
|
676
845
|
};
|
|
846
|
+
var deriveAutoDecisionBeacon = (toolName, toolInput, liveMode) => {
|
|
847
|
+
try {
|
|
848
|
+
if (liveMode.kill) return void 0;
|
|
849
|
+
const policy = readFreshCachedPolicy(getApiKey());
|
|
850
|
+
if (!policy) return void 0;
|
|
851
|
+
const resolved = resolvePolicy(policy, toolName, liveMode.mode, toolInput);
|
|
852
|
+
if (resolved.timeoutSeconds !== 0 || resolved.timeoutAction !== "approve") return void 0;
|
|
853
|
+
return {
|
|
854
|
+
decisionOrigin: resolveAutoResolveOrigin(policy, toolName, toolInput),
|
|
855
|
+
toolName,
|
|
856
|
+
toolTarget: deriveToolTarget(toolName, toolInput)
|
|
857
|
+
};
|
|
858
|
+
} catch {
|
|
859
|
+
return void 0;
|
|
860
|
+
}
|
|
861
|
+
};
|
|
862
|
+
var AUTO_DECISION_WINDOW_MS = 15 * 60 * 1e3;
|
|
863
|
+
var HOOK_ACTIVE_WINDOW_MS = 30 * 60 * 1e3;
|
|
677
864
|
var deriveUsage = (transcriptPath, sessionId) => {
|
|
678
865
|
if (!transcriptPath || process.env.PUSHARY_COST_TRACKING === "off") return void 0;
|
|
679
866
|
try {
|
|
@@ -682,6 +869,7 @@ var deriveUsage = (transcriptPath, sessionId) => {
|
|
|
682
869
|
return void 0;
|
|
683
870
|
}
|
|
684
871
|
};
|
|
872
|
+
var detectInstallMode = () => (process.argv[1] ?? "").includes("_npx") ? "npx" : "cli";
|
|
685
873
|
var reportEvent = async (event, options = {}) => {
|
|
686
874
|
const apiKey = getApiKey();
|
|
687
875
|
const baseUrl = getBaseUrl();
|
|
@@ -694,7 +882,8 @@ var reportEvent = async (event, options = {}) => {
|
|
|
694
882
|
},
|
|
695
883
|
body: JSON.stringify({
|
|
696
884
|
...event,
|
|
697
|
-
machineId: event.machineId ?? getMachineId()
|
|
885
|
+
machineId: event.machineId ?? getMachineId(),
|
|
886
|
+
installMode: detectInstallMode()
|
|
698
887
|
}),
|
|
699
888
|
signal: AbortSignal.timeout(options.timeoutMs ?? 1e4)
|
|
700
889
|
});
|
|
@@ -712,9 +901,12 @@ var handlePostToolUse = async (input, agent = CLAUDE_CODE_AGENT) => {
|
|
|
712
901
|
const lookup = toPolicyLookup(input.tool_name, input.tool_input);
|
|
713
902
|
const isError = isToolResultError(input.tool_result);
|
|
714
903
|
const receiptsEnabled = process.env.PUSHARY_RECEIPTS !== "off";
|
|
904
|
+
const sessionKey = input.session_id || DEFAULT_SESSION;
|
|
715
905
|
const liveMode = await fetchModeState(getApiKey(), input.session_id);
|
|
716
|
-
|
|
717
|
-
|
|
906
|
+
const decisionSource = deriveDecisionSource(lookup.tool, lookup.input, liveMode);
|
|
907
|
+
const beacon = decisionSource === "policy_auto" && throttlePass(`autodecision:${sessionKey}:${lookup.tool}`, AUTO_DECISION_WINDOW_MS) ? deriveAutoDecisionBeacon(lookup.tool, lookup.input, liveMode) : void 0;
|
|
908
|
+
const reports = [
|
|
909
|
+
cleanupPendingQuestions(sessionKey),
|
|
718
910
|
reportEvent({
|
|
719
911
|
event: isError ? "tool_error" : "tool_complete",
|
|
720
912
|
agentType: agent.type,
|
|
@@ -722,11 +914,26 @@ var handlePostToolUse = async (input, agent = CLAUDE_CODE_AGENT) => {
|
|
|
722
914
|
action,
|
|
723
915
|
sessionId: input.session_id,
|
|
724
916
|
error: isError ? String(input.tool_result?.error ?? input.tool_result?.stderr ?? "").slice(0, 500) : void 0,
|
|
725
|
-
decisionSource
|
|
917
|
+
decisionSource,
|
|
918
|
+
...beacon ? { decisionOrigin: beacon.decisionOrigin, toolName: beacon.toolName, toolTarget: beacon.toolTarget } : {},
|
|
726
919
|
meta: receiptsEnabled ? deriveReceiptMeta(lookup.tool, lookup.input, input.tool_result, input.cwd ?? process.cwd()) : void 0,
|
|
727
920
|
usage: deriveUsage(input.transcript_path, input.session_id)
|
|
728
921
|
})
|
|
729
|
-
]
|
|
922
|
+
];
|
|
923
|
+
if (throttlePass(`hookactive:${sessionKey}`, HOOK_ACTIVE_WINDOW_MS)) {
|
|
924
|
+
reports.push(
|
|
925
|
+
reportEvent(
|
|
926
|
+
{
|
|
927
|
+
event: "agent_hook_active",
|
|
928
|
+
agentType: agent.type,
|
|
929
|
+
agentName: `${agent.label} - ${projectName}`,
|
|
930
|
+
sessionId: input.session_id
|
|
931
|
+
},
|
|
932
|
+
{ maxAttempts: 1, timeoutMs: 800 }
|
|
933
|
+
)
|
|
934
|
+
);
|
|
935
|
+
}
|
|
936
|
+
await Promise.allSettled(reports);
|
|
730
937
|
} catch {
|
|
731
938
|
}
|
|
732
939
|
};
|
|
@@ -789,6 +996,8 @@ var handleNotification = async (input) => {
|
|
|
789
996
|
};
|
|
790
997
|
|
|
791
998
|
export {
|
|
999
|
+
isValidApiKey,
|
|
1000
|
+
DECISION_LINE_MAX,
|
|
792
1001
|
askUser,
|
|
793
1002
|
waitForAnswer,
|
|
794
1003
|
cancelQuestion,
|
|
@@ -802,9 +1011,9 @@ export {
|
|
|
802
1011
|
deriveActionBody,
|
|
803
1012
|
deriveAction,
|
|
804
1013
|
deriveBlocker,
|
|
1014
|
+
getMachineId,
|
|
805
1015
|
DEFAULT_SESSION,
|
|
806
1016
|
savePendingQuestion,
|
|
807
|
-
getMachineId,
|
|
808
1017
|
readLastUserPrompt,
|
|
809
1018
|
CODEX_AGENT,
|
|
810
1019
|
codexAllow,
|