@askance/cli 0.1.0 → 0.2.1
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/LICENSE +21 -21
- package/README.md +79 -79
- package/dist/cli/index.js +262 -114
- package/dist/common/api-client.js +13 -14
- package/package.json +3 -5
- package/dist/templates/ci-safe.yml +0 -22
- package/dist/templates/conservative.yml +0 -30
- package/dist/templates/copilot-config.json +0 -11
- package/dist/templates/cursor-config.json +0 -18
- package/dist/templates/moderate.yml +0 -34
- package/dist/templates/permissive.yml +0 -18
package/LICENSE
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2025 Advancer Limited
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Advancer Limited
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,79 +1,79 @@
|
|
|
1
|
-
# @askance/cli
|
|
2
|
-
|
|
3
|
-
Tool call interception & approval management for AI coding agents.
|
|
4
|
-
|
|
5
|
-
Askance hooks into your coding agent (Claude Code, Cursor, GitHub Copilot) and routes tool calls through policy rules and a cloud dashboard for approval — so agents can work while you review from any device.
|
|
6
|
-
|
|
7
|
-
## Quick Start
|
|
8
|
-
|
|
9
|
-
```bash
|
|
10
|
-
npm install --save-dev @askance/cli
|
|
11
|
-
npx askance init
|
|
12
|
-
npx askance login
|
|
13
|
-
# Restart your agent session
|
|
14
|
-
```
|
|
15
|
-
|
|
16
|
-
## What it does
|
|
17
|
-
|
|
18
|
-
- **Hook handler** — intercepts tool calls before execution and evaluates them against your `.askance.yml` policy rules
|
|
19
|
-
- **MCP server** — provides tools for the agent to wait for approvals and check for instructions
|
|
20
|
-
- **CLI** — sets up config files and authenticates with the Askance cloud
|
|
21
|
-
|
|
22
|
-
## Commands
|
|
23
|
-
|
|
24
|
-
| Command | Description |
|
|
25
|
-
|---------|-------------|
|
|
26
|
-
| `npx askance init` | Set up hooks, MCP server, policy rules, and CLAUDE.md |
|
|
27
|
-
| `npx askance login` | Authenticate via browser (GitHub, Google, or Microsoft) |
|
|
28
|
-
| `npx askance template <name>` | Apply a policy template (`conservative`, `moderate`, `permissive`, `ci-safe`) |
|
|
29
|
-
| `npx askance help` | Show usage information |
|
|
30
|
-
|
|
31
|
-
### Init options
|
|
32
|
-
|
|
33
|
-
```bash
|
|
34
|
-
npx askance init # Claude Code (default)
|
|
35
|
-
npx askance init --cursor # Cursor IDE
|
|
36
|
-
npx askance init --copilot # GitHub Copilot
|
|
37
|
-
npx askance init --all # All agents
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
## Policy rules
|
|
41
|
-
|
|
42
|
-
Policy rules in `.askance.yml` control what happens when the agent uses a tool:
|
|
43
|
-
|
|
44
|
-
- **allow** — tool call proceeds immediately
|
|
45
|
-
- **gate** — queued for approval in the dashboard
|
|
46
|
-
- **deny** — blocked automatically
|
|
47
|
-
|
|
48
|
-
```yaml
|
|
49
|
-
rules:
|
|
50
|
-
- name: "Allow read-only tools"
|
|
51
|
-
match:
|
|
52
|
-
tool: "^(Read|Glob|Grep|WebSearch)$"
|
|
53
|
-
action: allow
|
|
54
|
-
|
|
55
|
-
- name: "Gate file writes"
|
|
56
|
-
match:
|
|
57
|
-
tool: "^(Edit|Write)$"
|
|
58
|
-
action: gate
|
|
59
|
-
risk: medium
|
|
60
|
-
|
|
61
|
-
- name: "Deny destructive commands"
|
|
62
|
-
match:
|
|
63
|
-
tool: "^Bash$"
|
|
64
|
-
command: "(rm -rf|chmod 777)"
|
|
65
|
-
action: deny
|
|
66
|
-
risk: high
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
## Dashboard
|
|
70
|
-
|
|
71
|
-
Manage approvals at [app.askance.app](https://app.askance.app) — approve, deny, or send instructions to your agent from any device.
|
|
72
|
-
|
|
73
|
-
## Documentation
|
|
74
|
-
|
|
75
|
-
Full docs at [askance.app/docs](https://askance.app/docs)
|
|
76
|
-
|
|
77
|
-
## License
|
|
78
|
-
|
|
79
|
-
MIT
|
|
1
|
+
# @askance/cli
|
|
2
|
+
|
|
3
|
+
Tool call interception & approval management for AI coding agents.
|
|
4
|
+
|
|
5
|
+
Askance hooks into your coding agent (Claude Code, Cursor, GitHub Copilot) and routes tool calls through policy rules and a cloud dashboard for approval — so agents can work while you review from any device.
|
|
6
|
+
|
|
7
|
+
## Quick Start
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install --save-dev @askance/cli
|
|
11
|
+
npx askance init
|
|
12
|
+
npx askance login
|
|
13
|
+
# Restart your agent session
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## What it does
|
|
17
|
+
|
|
18
|
+
- **Hook handler** — intercepts tool calls before execution and evaluates them against your `.askance.yml` policy rules
|
|
19
|
+
- **MCP server** — provides tools for the agent to wait for approvals and check for instructions
|
|
20
|
+
- **CLI** — sets up config files and authenticates with the Askance cloud
|
|
21
|
+
|
|
22
|
+
## Commands
|
|
23
|
+
|
|
24
|
+
| Command | Description |
|
|
25
|
+
|---------|-------------|
|
|
26
|
+
| `npx askance init` | Set up hooks, MCP server, policy rules, and CLAUDE.md |
|
|
27
|
+
| `npx askance login` | Authenticate via browser (GitHub, Google, or Microsoft) |
|
|
28
|
+
| `npx askance template <name>` | Apply a policy template (`conservative`, `moderate`, `permissive`, `ci-safe`) |
|
|
29
|
+
| `npx askance help` | Show usage information |
|
|
30
|
+
|
|
31
|
+
### Init options
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
npx askance init # Claude Code (default)
|
|
35
|
+
npx askance init --cursor # Cursor IDE
|
|
36
|
+
npx askance init --copilot # GitHub Copilot
|
|
37
|
+
npx askance init --all # All agents
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Policy rules
|
|
41
|
+
|
|
42
|
+
Policy rules in `.askance.yml` control what happens when the agent uses a tool:
|
|
43
|
+
|
|
44
|
+
- **allow** — tool call proceeds immediately
|
|
45
|
+
- **gate** — queued for approval in the dashboard
|
|
46
|
+
- **deny** — blocked automatically
|
|
47
|
+
|
|
48
|
+
```yaml
|
|
49
|
+
rules:
|
|
50
|
+
- name: "Allow read-only tools"
|
|
51
|
+
match:
|
|
52
|
+
tool: "^(Read|Glob|Grep|WebSearch)$"
|
|
53
|
+
action: allow
|
|
54
|
+
|
|
55
|
+
- name: "Gate file writes"
|
|
56
|
+
match:
|
|
57
|
+
tool: "^(Edit|Write)$"
|
|
58
|
+
action: gate
|
|
59
|
+
risk: medium
|
|
60
|
+
|
|
61
|
+
- name: "Deny destructive commands"
|
|
62
|
+
match:
|
|
63
|
+
tool: "^Bash$"
|
|
64
|
+
command: "(rm -rf|chmod 777)"
|
|
65
|
+
action: deny
|
|
66
|
+
risk: high
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Dashboard
|
|
70
|
+
|
|
71
|
+
Manage approvals at [app.askance.app](https://app.askance.app) — approve, deny, or send instructions to your agent from any device.
|
|
72
|
+
|
|
73
|
+
## Documentation
|
|
74
|
+
|
|
75
|
+
Full docs at [askance.app/docs](https://askance.app/docs)
|
|
76
|
+
|
|
77
|
+
## License
|
|
78
|
+
|
|
79
|
+
MIT
|
package/dist/cli/index.js
CHANGED
|
@@ -39,6 +39,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
39
39
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
40
40
|
const fs_1 = __importDefault(require("fs"));
|
|
41
41
|
const path_1 = __importDefault(require("path"));
|
|
42
|
+
const child_process_1 = require("child_process");
|
|
43
|
+
const readline_1 = __importDefault(require("readline"));
|
|
42
44
|
const api_client_1 = require("../common/api-client");
|
|
43
45
|
const COMMANDS = ["init", "login", "template", "help"];
|
|
44
46
|
const command = (process.argv[2] ?? "help");
|
|
@@ -53,76 +55,42 @@ const pkgRoot = path_1.default.resolve(__dirname, "..", "..");
|
|
|
53
55
|
// ---------------------------------------------------------------------------
|
|
54
56
|
// Shared helpers
|
|
55
57
|
// ---------------------------------------------------------------------------
|
|
56
|
-
function
|
|
57
|
-
const
|
|
58
|
-
if (!fs_1.default.existsSync(
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
- name: "Allow safe bash commands"
|
|
76
|
-
match:
|
|
77
|
-
tool: "^Bash$"
|
|
78
|
-
command: "^(npm test|npm run lint|npm run build|git status|git diff|git log|ls)"
|
|
79
|
-
action: allow
|
|
80
|
-
|
|
81
|
-
- name: "Gate file writes"
|
|
82
|
-
match:
|
|
83
|
-
tool: "^(Edit|Write|NotebookEdit)$"
|
|
84
|
-
action: gate
|
|
85
|
-
risk: medium
|
|
86
|
-
|
|
87
|
-
- name: "Gate package installs"
|
|
88
|
-
match:
|
|
89
|
-
tool: "^Bash$"
|
|
90
|
-
command: "(npm install|pip install|dotnet add)"
|
|
91
|
-
action: gate
|
|
92
|
-
risk: medium
|
|
93
|
-
|
|
94
|
-
- name: "Deny destructive commands"
|
|
95
|
-
match:
|
|
96
|
-
tool: "^Bash$"
|
|
97
|
-
command: "(rm -rf|chmod 777|curl.*\\\\|.*bash)"
|
|
98
|
-
action: deny
|
|
99
|
-
risk: high
|
|
100
|
-
|
|
101
|
-
- name: "Default - gate everything else"
|
|
102
|
-
match:
|
|
103
|
-
tool: ".*"
|
|
104
|
-
action: gate
|
|
105
|
-
risk: low
|
|
106
|
-
`;
|
|
107
|
-
fs_1.default.writeFileSync(policyPath, defaultPolicy);
|
|
108
|
-
console.log("[askance] Created .askance.yml");
|
|
58
|
+
function ensureConfig() {
|
|
59
|
+
const askanceDir = path_1.default.join(projectDir, ".askance");
|
|
60
|
+
if (!fs_1.default.existsSync(askanceDir)) {
|
|
61
|
+
fs_1.default.mkdirSync(askanceDir, { recursive: true });
|
|
62
|
+
}
|
|
63
|
+
const configPath = path_1.default.join(askanceDir, "config.json");
|
|
64
|
+
if (!fs_1.default.existsSync(configPath)) {
|
|
65
|
+
const defaultConfig = {
|
|
66
|
+
apiUrl: "https://api.askance.app",
|
|
67
|
+
projectId: "",
|
|
68
|
+
keepAlive: {
|
|
69
|
+
enabled: true,
|
|
70
|
+
duration: 3600,
|
|
71
|
+
pollInterval: 30,
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
fs_1.default.writeFileSync(configPath, JSON.stringify(defaultConfig, null, 2) + "\n");
|
|
75
|
+
console.log("[askance] Created .askance/config.json");
|
|
109
76
|
}
|
|
110
77
|
else {
|
|
111
|
-
console.log("[askance] .askance.
|
|
78
|
+
console.log("[askance] .askance/config.json already exists, skipping");
|
|
112
79
|
}
|
|
113
80
|
}
|
|
114
81
|
function ensureGitignore() {
|
|
115
82
|
const gitignorePath = path_1.default.join(projectDir, ".gitignore");
|
|
83
|
+
const ignoreEntry = ".askance/credentials";
|
|
116
84
|
if (fs_1.default.existsSync(gitignorePath)) {
|
|
117
85
|
const content = fs_1.default.readFileSync(gitignorePath, "utf-8");
|
|
118
|
-
if (!content.includes(
|
|
119
|
-
fs_1.default.appendFileSync(gitignorePath, "\n# Askance
|
|
120
|
-
console.log("[askance] Added .askance/ to .gitignore");
|
|
86
|
+
if (!content.includes(ignoreEntry)) {
|
|
87
|
+
fs_1.default.appendFileSync(gitignorePath, "\n# Askance credentials (do not commit)\n.askance/credentials\n");
|
|
88
|
+
console.log("[askance] Added .askance/credentials to .gitignore");
|
|
121
89
|
}
|
|
122
90
|
}
|
|
123
91
|
else {
|
|
124
|
-
fs_1.default.writeFileSync(gitignorePath, "# Askance
|
|
125
|
-
console.log("[askance] Created .gitignore with .askance/");
|
|
92
|
+
fs_1.default.writeFileSync(gitignorePath, "# Askance credentials (do not commit)\n.askance/credentials\n");
|
|
93
|
+
console.log("[askance] Created .gitignore with .askance/credentials");
|
|
126
94
|
}
|
|
127
95
|
}
|
|
128
96
|
// ---------------------------------------------------------------------------
|
|
@@ -137,9 +105,13 @@ function initClaude() {
|
|
|
137
105
|
const hookHandlerPath = path_1.default.join(pkgRoot, "dist", "hook-handler", "index.js").replace(/\\/g, "/");
|
|
138
106
|
const stopHookPath = path_1.default.join(pkgRoot, "dist", "hook-handler", "stop-hook.js").replace(/\\/g, "/");
|
|
139
107
|
const mcpServerPath = path_1.default.join(pkgRoot, "dist", "mcp-server", "index.js").replace(/\\/g, "/");
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
108
|
+
let settings = {};
|
|
109
|
+
if (fs_1.default.existsSync(settingsPath)) {
|
|
110
|
+
try {
|
|
111
|
+
settings = JSON.parse(fs_1.default.readFileSync(settingsPath, "utf-8"));
|
|
112
|
+
}
|
|
113
|
+
catch { }
|
|
114
|
+
}
|
|
143
115
|
// Set hooks
|
|
144
116
|
const hooks = (settings.hooks ?? {});
|
|
145
117
|
hooks.PreToolUse = [
|
|
@@ -188,9 +160,13 @@ function initCursor() {
|
|
|
188
160
|
const settingsPath = path_1.default.join(cursorDir, "settings.json");
|
|
189
161
|
const hookHandlerPath = path_1.default.join(pkgRoot, "dist", "hook-handler", "index.js").replace(/\\/g, "/");
|
|
190
162
|
const mcpServerPath = path_1.default.join(pkgRoot, "dist", "mcp-server", "index.js").replace(/\\/g, "/");
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
163
|
+
let settings = {};
|
|
164
|
+
if (fs_1.default.existsSync(settingsPath)) {
|
|
165
|
+
try {
|
|
166
|
+
settings = JSON.parse(fs_1.default.readFileSync(settingsPath, "utf-8"));
|
|
167
|
+
}
|
|
168
|
+
catch { }
|
|
169
|
+
}
|
|
194
170
|
// Set Cursor hooks
|
|
195
171
|
const cursorHooks = (settings["cursor.hooks"] ?? {});
|
|
196
172
|
cursorHooks.preToolUse = {
|
|
@@ -222,9 +198,13 @@ function initCopilot() {
|
|
|
222
198
|
}
|
|
223
199
|
const configPath = path_1.default.join(githubDir, "copilot-config.json");
|
|
224
200
|
const hookHandlerPath = path_1.default.join(pkgRoot, "dist", "hook-handler", "index.js").replace(/\\/g, "/");
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
201
|
+
let config = {};
|
|
202
|
+
if (fs_1.default.existsSync(configPath)) {
|
|
203
|
+
try {
|
|
204
|
+
config = JSON.parse(fs_1.default.readFileSync(configPath, "utf-8"));
|
|
205
|
+
}
|
|
206
|
+
catch { }
|
|
207
|
+
}
|
|
228
208
|
// Set Copilot agent hooks
|
|
229
209
|
const copilotAgent = (config["copilot.agent"] ?? {});
|
|
230
210
|
const hooks = (copilotAgent.hooks ?? {});
|
|
@@ -247,7 +227,7 @@ const CLAUDE_MD_SECTION = `
|
|
|
247
227
|
This project uses [Askance](https://askance.app) for AI tool call interception and approval management.
|
|
248
228
|
|
|
249
229
|
**How it works:**
|
|
250
|
-
- All tool calls are intercepted by Askance hooks and evaluated against policy rules
|
|
230
|
+
- All tool calls are intercepted by Askance hooks and evaluated against policy rules managed via the dashboard
|
|
251
231
|
- Safe operations (reads, searches) are auto-approved; risky operations require human approval
|
|
252
232
|
- When a tool call is gated for approval, use \`mcp__askance__wait\` to wait for the decision
|
|
253
233
|
- Use \`mcp__askance__check_instructions\` periodically to check for operator instructions
|
|
@@ -275,8 +255,8 @@ function ensureClaudeMd() {
|
|
|
275
255
|
}
|
|
276
256
|
function init() {
|
|
277
257
|
console.log("[askance] Initializing Askance in", projectDir);
|
|
278
|
-
// 1. Always
|
|
279
|
-
|
|
258
|
+
// 1. Always create .askance/config.json + update .gitignore
|
|
259
|
+
ensureConfig();
|
|
280
260
|
ensureGitignore();
|
|
281
261
|
// 2. Determine which IDE configs to set up
|
|
282
262
|
const setupClaude = flagAll || (!flagCursor && !flagCopilot);
|
|
@@ -305,7 +285,139 @@ function init() {
|
|
|
305
285
|
console.log(" 2. Copilot agent hooks are configured in .github/copilot-config.json");
|
|
306
286
|
}
|
|
307
287
|
console.log(" 3. Open the dashboard: https://app.askance.app");
|
|
308
|
-
console.log(" 4.
|
|
288
|
+
console.log(" 4. Manage policy rules via the dashboard");
|
|
289
|
+
}
|
|
290
|
+
// ---------------------------------------------------------------------------
|
|
291
|
+
// Project setup helpers
|
|
292
|
+
// ---------------------------------------------------------------------------
|
|
293
|
+
function detectGitInfo() {
|
|
294
|
+
let repoUrl = "";
|
|
295
|
+
let branch = "main";
|
|
296
|
+
let repoName = path_1.default.basename(projectDir);
|
|
297
|
+
try {
|
|
298
|
+
repoUrl = (0, child_process_1.execSync)("git remote get-url origin", {
|
|
299
|
+
cwd: projectDir,
|
|
300
|
+
encoding: "utf-8",
|
|
301
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
302
|
+
}).trim();
|
|
303
|
+
}
|
|
304
|
+
catch { }
|
|
305
|
+
try {
|
|
306
|
+
branch = (0, child_process_1.execSync)("git branch --show-current", {
|
|
307
|
+
cwd: projectDir,
|
|
308
|
+
encoding: "utf-8",
|
|
309
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
310
|
+
}).trim() || "main";
|
|
311
|
+
}
|
|
312
|
+
catch { }
|
|
313
|
+
// Extract repo name from URL (e.g. git@github.com:user/my-repo.git → my-repo)
|
|
314
|
+
if (repoUrl) {
|
|
315
|
+
const match = repoUrl.match(/\/([^/]+?)(?:\.git)?$/) ?? repoUrl.match(/:([^/]+?)(?:\.git)?$/);
|
|
316
|
+
if (match)
|
|
317
|
+
repoName = match[1];
|
|
318
|
+
}
|
|
319
|
+
return { repoUrl, branch, repoName };
|
|
320
|
+
}
|
|
321
|
+
function promptSelection(message, max) {
|
|
322
|
+
const rl = readline_1.default.createInterface({ input: process.stdin, output: process.stdout });
|
|
323
|
+
return new Promise((resolve) => {
|
|
324
|
+
rl.question(message, (answer) => {
|
|
325
|
+
rl.close();
|
|
326
|
+
const num = parseInt(answer.trim(), 10);
|
|
327
|
+
if (isNaN(num) || num < 1 || num > max) {
|
|
328
|
+
resolve(-1);
|
|
329
|
+
}
|
|
330
|
+
else {
|
|
331
|
+
resolve(num);
|
|
332
|
+
}
|
|
333
|
+
});
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
async function setupProject() {
|
|
337
|
+
console.log("\n[askance] Setting up project...");
|
|
338
|
+
// Fetch existing projects
|
|
339
|
+
const projectsResult = await (0, api_client_1.apiGet)("/api/projects");
|
|
340
|
+
if (!projectsResult.ok || !projectsResult.data) {
|
|
341
|
+
console.log(" Could not fetch projects. You can link a project later via the dashboard.");
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
const projects = projectsResult.data;
|
|
345
|
+
let selectedId = "";
|
|
346
|
+
let selectedName = "";
|
|
347
|
+
if (projects.length === 1) {
|
|
348
|
+
// Auto-select the only project
|
|
349
|
+
selectedId = projects[0].id;
|
|
350
|
+
selectedName = projects[0].name;
|
|
351
|
+
console.log(` Found 1 existing project: ${selectedName}`);
|
|
352
|
+
console.log(` Using project: ${selectedName} (${selectedId})`);
|
|
353
|
+
}
|
|
354
|
+
else if (projects.length > 1) {
|
|
355
|
+
// Prompt the user to select
|
|
356
|
+
console.log(` Found ${projects.length} projects:`);
|
|
357
|
+
for (let i = 0; i < projects.length; i++) {
|
|
358
|
+
console.log(` ${i + 1}. ${projects[i].name}`);
|
|
359
|
+
}
|
|
360
|
+
const choice = await promptSelection(` Select a project (1-${projects.length}): `, projects.length);
|
|
361
|
+
if (choice < 1) {
|
|
362
|
+
console.log(" Invalid selection. You can link a project later via the dashboard.");
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
selectedId = projects[choice - 1].id;
|
|
366
|
+
selectedName = projects[choice - 1].name;
|
|
367
|
+
console.log(` Using project: ${selectedName} (${selectedId})`);
|
|
368
|
+
}
|
|
369
|
+
else {
|
|
370
|
+
// No projects — auto-create one
|
|
371
|
+
const git = detectGitInfo();
|
|
372
|
+
console.log(` No projects found. Creating "${git.repoName}" from ${git.repoUrl ? "git remote" : "directory name"}...`);
|
|
373
|
+
const createResult = await (0, api_client_1.apiPost)("/api/projects", {
|
|
374
|
+
Name: git.repoName,
|
|
375
|
+
RepoUrl: git.repoUrl,
|
|
376
|
+
Branch: git.branch,
|
|
377
|
+
});
|
|
378
|
+
if (!createResult.ok) {
|
|
379
|
+
if (createResult.status === 402) {
|
|
380
|
+
console.log("\n Your plan allows 1 project and you've reached the limit.");
|
|
381
|
+
console.log(" Upgrade at https://app.askance.app/settings");
|
|
382
|
+
console.log(" You're still logged in — link a project manually or upgrade first.");
|
|
383
|
+
}
|
|
384
|
+
else {
|
|
385
|
+
console.log(` Failed to create project: ${createResult.error}`);
|
|
386
|
+
console.log(" You can create a project later via the dashboard.");
|
|
387
|
+
}
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
390
|
+
if (!createResult.data) {
|
|
391
|
+
console.log(" Failed to create project. You can create one later via the dashboard.");
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
selectedId = createResult.data.id;
|
|
395
|
+
selectedName = createResult.data.name;
|
|
396
|
+
console.log(` Created project "${selectedName}"`);
|
|
397
|
+
console.log(` Project ID: ${selectedId}`);
|
|
398
|
+
}
|
|
399
|
+
// Save project ID to .askance/config.json
|
|
400
|
+
const configPath = path_1.default.join(projectDir, ".askance", "config.json");
|
|
401
|
+
if (fs_1.default.existsSync(configPath)) {
|
|
402
|
+
try {
|
|
403
|
+
const cfg = JSON.parse(fs_1.default.readFileSync(configPath, "utf-8"));
|
|
404
|
+
cfg.projectId = selectedId;
|
|
405
|
+
fs_1.default.writeFileSync(configPath, JSON.stringify(cfg, null, 2) + "\n");
|
|
406
|
+
}
|
|
407
|
+
catch { }
|
|
408
|
+
}
|
|
409
|
+
// Also save to credentials for backward compatibility
|
|
410
|
+
const credPath = path_1.default.join(projectDir, ".askance", "credentials");
|
|
411
|
+
if (fs_1.default.existsSync(credPath)) {
|
|
412
|
+
try {
|
|
413
|
+
const creds = JSON.parse(fs_1.default.readFileSync(credPath, "utf-8"));
|
|
414
|
+
creds.project_id = selectedId;
|
|
415
|
+
fs_1.default.writeFileSync(credPath, JSON.stringify(creds, null, 2) + "\n", { mode: 0o600 });
|
|
416
|
+
}
|
|
417
|
+
catch { }
|
|
418
|
+
}
|
|
419
|
+
(0, api_client_1.clearConfigCache)();
|
|
420
|
+
console.log(" Project linked successfully.");
|
|
309
421
|
}
|
|
310
422
|
// ---------------------------------------------------------------------------
|
|
311
423
|
// Login — opens browser for auth, saves JWT to .askance/credentials
|
|
@@ -382,13 +494,16 @@ async function login() {
|
|
|
382
494
|
project_id: pollResult.data.projectId ?? "",
|
|
383
495
|
};
|
|
384
496
|
fs_1.default.writeFileSync(path_1.default.join(askanceDir, "credentials"), JSON.stringify(credentials, null, 2) + "\n", { mode: 0o600 });
|
|
385
|
-
// Update .askance.
|
|
497
|
+
// Update .askance/config.json project_id if we got one
|
|
386
498
|
if (pollResult.data.projectId) {
|
|
387
|
-
const
|
|
388
|
-
if (fs_1.default.existsSync(
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
499
|
+
const configPath = path_1.default.join(projectDir, ".askance", "config.json");
|
|
500
|
+
if (fs_1.default.existsSync(configPath)) {
|
|
501
|
+
try {
|
|
502
|
+
const cfg = JSON.parse(fs_1.default.readFileSync(configPath, "utf-8"));
|
|
503
|
+
cfg.projectId = pollResult.data.projectId;
|
|
504
|
+
fs_1.default.writeFileSync(configPath, JSON.stringify(cfg, null, 2) + "\n");
|
|
505
|
+
}
|
|
506
|
+
catch { }
|
|
392
507
|
}
|
|
393
508
|
}
|
|
394
509
|
(0, api_client_1.clearConfigCache)();
|
|
@@ -397,6 +512,10 @@ async function login() {
|
|
|
397
512
|
console.log(` Logged in as: ${pollResult.data.email}`);
|
|
398
513
|
}
|
|
399
514
|
console.log(" Credentials saved to .askance/credentials");
|
|
515
|
+
// If the server didn't provide a project_id, run interactive setup
|
|
516
|
+
if (!pollResult.data.projectId) {
|
|
517
|
+
await setupProject();
|
|
518
|
+
}
|
|
400
519
|
return;
|
|
401
520
|
}
|
|
402
521
|
if (pollResult.data.status === "expired") {
|
|
@@ -408,8 +527,35 @@ async function login() {
|
|
|
408
527
|
console.error("\n[askance] Login timed out. Please try again.");
|
|
409
528
|
process.exit(1);
|
|
410
529
|
}
|
|
411
|
-
|
|
412
|
-
|
|
530
|
+
const TEMPLATE_RULES = {
|
|
531
|
+
conservative: [
|
|
532
|
+
{ Name: "Allow read-only tools", ToolPattern: "^(Read|Glob|Grep|WebSearch|WebFetch)$", Action: "Allow", Risk: "Low" },
|
|
533
|
+
{ Name: "Gate file writes", ToolPattern: "^(Edit|Write|NotebookEdit)$", Action: "Gate", Risk: "Medium" },
|
|
534
|
+
{ Name: "Gate all bash commands", ToolPattern: "^Bash$", Action: "Gate", Risk: "Medium" },
|
|
535
|
+
{ Name: "Deny destructive commands", ToolPattern: "^Bash$", CommandPattern: "(rm -rf|chmod 777|curl.*\\\\|.*bash)", Action: "Deny", Risk: "High" },
|
|
536
|
+
{ Name: "Default — gate everything else", ToolPattern: ".*", Action: "Gate", Risk: "Low" },
|
|
537
|
+
],
|
|
538
|
+
moderate: [
|
|
539
|
+
{ Name: "Allow read-only tools", ToolPattern: "^(Read|Glob|Grep|WebSearch|WebFetch)$", Action: "Allow", Risk: "Low" },
|
|
540
|
+
{ Name: "Allow safe bash commands", ToolPattern: "^Bash$", CommandPattern: "^(npm test|npm run lint|npm run build|git status|git diff|git log|ls)", Action: "Allow", Risk: "Low" },
|
|
541
|
+
{ Name: "Gate file writes", ToolPattern: "^(Edit|Write|NotebookEdit)$", Action: "Gate", Risk: "Medium" },
|
|
542
|
+
{ Name: "Gate package installs", ToolPattern: "^Bash$", CommandPattern: "(npm install|pip install|dotnet add)", Action: "Gate", Risk: "Medium" },
|
|
543
|
+
{ Name: "Deny destructive commands", ToolPattern: "^Bash$", CommandPattern: "(rm -rf|chmod 777|curl.*\\\\|.*bash)", Action: "Deny", Risk: "High" },
|
|
544
|
+
{ Name: "Default — gate everything else", ToolPattern: ".*", Action: "Gate", Risk: "Low" },
|
|
545
|
+
],
|
|
546
|
+
permissive: [
|
|
547
|
+
{ Name: "Deny destructive commands", ToolPattern: "^Bash$", CommandPattern: "(rm -rf|chmod 777|curl.*\\\\|.*bash)", Action: "Deny", Risk: "High" },
|
|
548
|
+
{ Name: "Default — allow everything else", ToolPattern: ".*", Action: "Allow", Risk: "Low" },
|
|
549
|
+
],
|
|
550
|
+
"ci-safe": [
|
|
551
|
+
{ Name: "Allow read-only tools", ToolPattern: "^(Read|Glob|Grep)$", Action: "Allow", Risk: "Low" },
|
|
552
|
+
{ Name: "Allow test and build commands", ToolPattern: "^Bash$", CommandPattern: "^(npm test|npm run lint|npm run build)", Action: "Allow", Risk: "Low" },
|
|
553
|
+
{ Name: "Deny all bash", ToolPattern: "^Bash$", Action: "Deny", Risk: "High" },
|
|
554
|
+
{ Name: "Default — deny everything else", ToolPattern: ".*", Action: "Deny", Risk: "Medium" },
|
|
555
|
+
],
|
|
556
|
+
};
|
|
557
|
+
async function template() {
|
|
558
|
+
const TEMPLATES = Object.keys(TEMPLATE_RULES);
|
|
413
559
|
const templateName = process.argv[3];
|
|
414
560
|
if (!templateName) {
|
|
415
561
|
console.log(`
|
|
@@ -417,7 +563,7 @@ function template() {
|
|
|
417
563
|
|
|
418
564
|
Available templates:
|
|
419
565
|
conservative Gate writes and bash, deny destructive commands
|
|
420
|
-
moderate Allow safe writes and bash, gate the rest
|
|
566
|
+
moderate Allow safe writes and bash, gate the rest (default)
|
|
421
567
|
permissive Allow everything, deny only destructive commands
|
|
422
568
|
ci-safe Minimal permissions for CI/CD environments
|
|
423
569
|
|
|
@@ -434,26 +580,21 @@ Example:
|
|
|
434
580
|
console.error(`[askance] Available templates: ${TEMPLATES.join(", ")}`);
|
|
435
581
|
process.exit(1);
|
|
436
582
|
}
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
const distTemplatePath = path_1.default.join(pkgRoot, "dist", "templates", `${templateName}.yml`);
|
|
442
|
-
const srcTemplatePath = path_1.default.join(pkgRoot, "src", "templates", `${templateName}.yml`);
|
|
443
|
-
const templatePath = fs_1.default.existsSync(distTemplatePath) ? distTemplatePath : srcTemplatePath;
|
|
444
|
-
if (!fs_1.default.existsSync(templatePath)) {
|
|
445
|
-
console.error(`[askance] Template file not found at ${templatePath}`);
|
|
446
|
-
console.error("[askance] Try rebuilding: npm run build");
|
|
583
|
+
const config = (0, api_client_1.readConfig)();
|
|
584
|
+
if (!config.token || !config.projectId) {
|
|
585
|
+
console.error("[askance] You must be logged in and have a project linked.");
|
|
586
|
+
console.error(" Run: npx askance login");
|
|
447
587
|
process.exit(1);
|
|
448
588
|
}
|
|
449
|
-
const
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
589
|
+
const rules = TEMPLATE_RULES[templateName];
|
|
590
|
+
console.log(`[askance] Applying "${templateName}" template (${rules.length} rules)...`);
|
|
591
|
+
const result = await (0, api_client_1.apiPost)(`/api/projects/${config.projectId}/rules`, rules);
|
|
592
|
+
if (!result.ok) {
|
|
593
|
+
console.error(`[askance] Failed to apply template: ${result.error}`);
|
|
594
|
+
process.exit(1);
|
|
453
595
|
}
|
|
454
|
-
|
|
455
|
-
console.log(
|
|
456
|
-
console.log("[askance] Restart your agent session to apply the new policy");
|
|
596
|
+
console.log(`[askance] Applied "${templateName}" template to project rules`);
|
|
597
|
+
console.log("[askance] Rules are active immediately — no restart needed");
|
|
457
598
|
}
|
|
458
599
|
function help() {
|
|
459
600
|
console.log(`
|
|
@@ -464,7 +605,7 @@ Usage:
|
|
|
464
605
|
|
|
465
606
|
Commands:
|
|
466
607
|
init Set up Askance in the current project
|
|
467
|
-
- Creates .askance.
|
|
608
|
+
- Creates .askance/config.json (settings)
|
|
468
609
|
- Configures agent hooks and MCP servers
|
|
469
610
|
|
|
470
611
|
Options:
|
|
@@ -477,7 +618,8 @@ Commands:
|
|
|
477
618
|
- Opens browser for OAuth sign-in
|
|
478
619
|
- Saves access token to .askance/credentials
|
|
479
620
|
|
|
480
|
-
template Apply a pre-built policy template
|
|
621
|
+
template Apply a pre-built policy template to your project
|
|
622
|
+
- Pushes rules to the Askance API (requires login)
|
|
481
623
|
- Available: conservative, moderate, permissive, ci-safe
|
|
482
624
|
- Usage: npx askance template <name>
|
|
483
625
|
|
|
@@ -497,19 +639,25 @@ Dashboard: https://app.askance.app
|
|
|
497
639
|
Docs: https://askance.app
|
|
498
640
|
`);
|
|
499
641
|
}
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
init
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
login
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
template
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
642
|
+
async function main() {
|
|
643
|
+
switch (command) {
|
|
644
|
+
case "init":
|
|
645
|
+
init();
|
|
646
|
+
break;
|
|
647
|
+
case "login":
|
|
648
|
+
await login();
|
|
649
|
+
break;
|
|
650
|
+
case "template":
|
|
651
|
+
await template();
|
|
652
|
+
break;
|
|
653
|
+
case "help":
|
|
654
|
+
default:
|
|
655
|
+
help();
|
|
656
|
+
break;
|
|
657
|
+
}
|
|
514
658
|
}
|
|
659
|
+
main().catch((err) => {
|
|
660
|
+
console.error("[askance] Error:", err.message);
|
|
661
|
+
process.exit(1);
|
|
662
|
+
});
|
|
515
663
|
//# sourceMappingURL=index.js.map
|
|
@@ -15,23 +15,22 @@ const node_https_1 = __importDefault(require("node:https"));
|
|
|
15
15
|
const node_http_1 = __importDefault(require("node:http"));
|
|
16
16
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
17
17
|
const node_path_1 = __importDefault(require("node:path"));
|
|
18
|
-
const yaml_1 = require("yaml");
|
|
19
18
|
let cachedConfig = null;
|
|
20
19
|
function readConfig() {
|
|
21
20
|
if (cachedConfig)
|
|
22
21
|
return cachedConfig;
|
|
23
22
|
const projectDir = process.env.ASKANCE_PROJECT_DIR ?? process.cwd();
|
|
24
|
-
// Read
|
|
23
|
+
// Read from .askance/config.json (primary) or env
|
|
25
24
|
let apiUrl = process.env.ASKANCE_API_URL ?? "";
|
|
26
25
|
let projectId = process.env.ASKANCE_PROJECT_ID ?? "";
|
|
27
|
-
const
|
|
28
|
-
if (node_fs_1.default.existsSync(
|
|
26
|
+
const configPath = node_path_1.default.join(projectDir, ".askance", "config.json");
|
|
27
|
+
if (node_fs_1.default.existsSync(configPath)) {
|
|
29
28
|
try {
|
|
30
|
-
const
|
|
31
|
-
if (
|
|
32
|
-
apiUrl =
|
|
33
|
-
if (
|
|
34
|
-
projectId =
|
|
29
|
+
const cfg = JSON.parse(node_fs_1.default.readFileSync(configPath, "utf-8"));
|
|
30
|
+
if (cfg.apiUrl && !apiUrl)
|
|
31
|
+
apiUrl = cfg.apiUrl;
|
|
32
|
+
if (cfg.projectId && !projectId)
|
|
33
|
+
projectId = cfg.projectId;
|
|
35
34
|
}
|
|
36
35
|
catch { }
|
|
37
36
|
}
|
|
@@ -170,13 +169,13 @@ function waitForInstruction(projectId, timeoutMs) {
|
|
|
170
169
|
}
|
|
171
170
|
function readKeepAliveConfig() {
|
|
172
171
|
const projectDir = process.env.ASKANCE_PROJECT_DIR ?? process.cwd();
|
|
173
|
-
const
|
|
172
|
+
const configPath = node_path_1.default.join(projectDir, ".askance", "config.json");
|
|
174
173
|
try {
|
|
175
|
-
const
|
|
174
|
+
const cfg = JSON.parse(node_fs_1.default.readFileSync(configPath, "utf-8"));
|
|
176
175
|
return {
|
|
177
|
-
enabled:
|
|
178
|
-
duration:
|
|
179
|
-
poll_interval:
|
|
176
|
+
enabled: cfg.keepAlive?.enabled ?? true,
|
|
177
|
+
duration: cfg.keepAlive?.duration ?? 3600,
|
|
178
|
+
poll_interval: cfg.keepAlive?.pollInterval ?? 30,
|
|
180
179
|
};
|
|
181
180
|
}
|
|
182
181
|
catch {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@askance/cli",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Askance CLI — Tool call interception & approval management for AI coding agents",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://askance.app",
|
|
@@ -29,19 +29,17 @@
|
|
|
29
29
|
},
|
|
30
30
|
"files": [
|
|
31
31
|
"dist/**/*.js",
|
|
32
|
-
"dist/templates/**",
|
|
33
32
|
"!dist/daemon/**",
|
|
34
33
|
"!dist/dashboard/**",
|
|
35
34
|
"LICENSE",
|
|
36
35
|
"README.md"
|
|
37
36
|
],
|
|
38
37
|
"scripts": {
|
|
39
|
-
"build": "tsc
|
|
38
|
+
"build": "tsc"
|
|
40
39
|
},
|
|
41
40
|
"dependencies": {
|
|
42
41
|
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
43
|
-
"uuid": "^11.1.0"
|
|
44
|
-
"yaml": "^2.6.1"
|
|
42
|
+
"uuid": "^11.1.0"
|
|
45
43
|
},
|
|
46
44
|
"devDependencies": {
|
|
47
45
|
"@types/uuid": "^10.0.0",
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
version: 1
|
|
2
|
-
|
|
3
|
-
api_url: https://api.askance.app
|
|
4
|
-
project_id: ""
|
|
5
|
-
|
|
6
|
-
keep_alive:
|
|
7
|
-
enabled: false
|
|
8
|
-
duration: 0
|
|
9
|
-
poll_interval: 0
|
|
10
|
-
rules:
|
|
11
|
-
- name: "Allow read-only tools"
|
|
12
|
-
match: { tool: "^(Read|Glob|Grep)$" }
|
|
13
|
-
action: allow
|
|
14
|
-
risk: low
|
|
15
|
-
- name: "Allow test commands"
|
|
16
|
-
match: { tool: "^Bash$", command: "^(npm test|pytest|dotnet test|go test|cargo test)" }
|
|
17
|
-
action: allow
|
|
18
|
-
risk: low
|
|
19
|
-
- name: "Deny everything else"
|
|
20
|
-
match: { tool: ".*" }
|
|
21
|
-
action: deny
|
|
22
|
-
risk: high
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
version: 1
|
|
2
|
-
|
|
3
|
-
api_url: https://api.askance.app
|
|
4
|
-
project_id: ""
|
|
5
|
-
|
|
6
|
-
keep_alive:
|
|
7
|
-
enabled: true
|
|
8
|
-
duration: 3600
|
|
9
|
-
poll_interval: 30
|
|
10
|
-
rules:
|
|
11
|
-
- name: "Allow read-only tools"
|
|
12
|
-
match: { tool: "^(Read|Glob|Grep|WebSearch|WebFetch)$" }
|
|
13
|
-
action: allow
|
|
14
|
-
risk: low
|
|
15
|
-
- name: "Gate all writes"
|
|
16
|
-
match: { tool: "^(Write|Edit|NotebookEdit)$" }
|
|
17
|
-
action: gate
|
|
18
|
-
risk: medium
|
|
19
|
-
- name: "Gate bash commands"
|
|
20
|
-
match: { tool: "^Bash$" }
|
|
21
|
-
action: gate
|
|
22
|
-
risk: high
|
|
23
|
-
- name: "Deny destructive commands"
|
|
24
|
-
match: { tool: "^Bash$", command: "(rm -rf|drop table|format |shutdown|reboot)" }
|
|
25
|
-
action: deny
|
|
26
|
-
risk: high
|
|
27
|
-
- name: "Default - gate everything"
|
|
28
|
-
match: { tool: ".*" }
|
|
29
|
-
action: gate
|
|
30
|
-
risk: medium
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"cursor.hooks": {
|
|
3
|
-
"preToolUse": {
|
|
4
|
-
"command": "node",
|
|
5
|
-
"args": ["node_modules/@askance/cli/dist/hook-handler/index.js"],
|
|
6
|
-
"matchTools": [".*"],
|
|
7
|
-
"excludeTools": ["mcp__askance__.*"]
|
|
8
|
-
}
|
|
9
|
-
},
|
|
10
|
-
"cursor.mcp": {
|
|
11
|
-
"servers": {
|
|
12
|
-
"askance": {
|
|
13
|
-
"command": "node",
|
|
14
|
-
"args": ["node_modules/@askance/cli/dist/mcp-server/index.js"]
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
}
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
version: 1
|
|
2
|
-
|
|
3
|
-
api_url: https://api.askance.app
|
|
4
|
-
project_id: ""
|
|
5
|
-
|
|
6
|
-
keep_alive:
|
|
7
|
-
enabled: true
|
|
8
|
-
duration: 3600
|
|
9
|
-
poll_interval: 30
|
|
10
|
-
rules:
|
|
11
|
-
- name: "Allow read-only tools"
|
|
12
|
-
match: { tool: "^(Read|Glob|Grep|WebSearch|WebFetch)$" }
|
|
13
|
-
action: allow
|
|
14
|
-
risk: low
|
|
15
|
-
- name: "Allow safe writes"
|
|
16
|
-
match: { tool: "^(Write|Edit|NotebookEdit)$" }
|
|
17
|
-
action: allow
|
|
18
|
-
risk: low
|
|
19
|
-
- name: "Allow safe bash commands"
|
|
20
|
-
match: { tool: "^Bash$", command: "^(npm test|npm run |npx tsc|git status|git log|git diff|ls |cat |echo |pwd)" }
|
|
21
|
-
action: allow
|
|
22
|
-
risk: low
|
|
23
|
-
- name: "Gate other bash commands"
|
|
24
|
-
match: { tool: "^Bash$" }
|
|
25
|
-
action: gate
|
|
26
|
-
risk: medium
|
|
27
|
-
- name: "Deny destructive commands"
|
|
28
|
-
match: { tool: "^Bash$", command: "(rm -rf|drop table|format |shutdown|reboot|git push --force)" }
|
|
29
|
-
action: deny
|
|
30
|
-
risk: high
|
|
31
|
-
- name: "Default - gate everything"
|
|
32
|
-
match: { tool: ".*" }
|
|
33
|
-
action: gate
|
|
34
|
-
risk: low
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
version: 1
|
|
2
|
-
|
|
3
|
-
api_url: https://api.askance.app
|
|
4
|
-
project_id: ""
|
|
5
|
-
|
|
6
|
-
keep_alive:
|
|
7
|
-
enabled: true
|
|
8
|
-
duration: 3600
|
|
9
|
-
poll_interval: 30
|
|
10
|
-
rules:
|
|
11
|
-
- name: "Allow all tools"
|
|
12
|
-
match: { tool: ".*" }
|
|
13
|
-
action: allow
|
|
14
|
-
risk: low
|
|
15
|
-
- name: "Deny destructive commands"
|
|
16
|
-
match: { tool: "^Bash$", command: "(rm -rf /|drop database|format c:|shutdown|reboot|git push --force.*main)" }
|
|
17
|
-
action: deny
|
|
18
|
-
risk: high
|