@hanhnd/agent-kit 1.0.5 → 1.0.7
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/README.md +16 -4
- package/dist/kit-server.js +42 -65
- package/dist/tools/core.js +1 -30
- package/dist/tools/security.d.ts +2 -3
- package/dist/tools/security.js +22 -4
- package/dist/utils.d.ts +0 -6
- package/dist/utils.js +0 -15
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -11,6 +11,8 @@
|
|
|
11
11
|
| `/ak:brainstorm [idea]` | Strategic architectural analysis |
|
|
12
12
|
| `/ak:plan [file or idea]` | Create an implementation blueprint |
|
|
13
13
|
| `/ak:code [file or task]` | Implement from a plan |
|
|
14
|
+
| `/ak:code-simplify` | Refactor modified code for readability |
|
|
15
|
+
| `/ak:research [topic]` | Research a topic |
|
|
14
16
|
| `/ak:review-pr [PR URL]` | Review a pull request |
|
|
15
17
|
| `/ak:review` | Review uncommitted local changes |
|
|
16
18
|
| `/ak:ticket [ID]` | Fetch a Jira ticket and plan from it |
|
|
@@ -27,7 +29,7 @@
|
|
|
27
29
|
|
|
28
30
|
```bash
|
|
29
31
|
claude plugin marketplace add https://github.com/hanh-nd/agent-kit
|
|
30
|
-
claude plugin install
|
|
32
|
+
claude plugin install ak
|
|
31
33
|
```
|
|
32
34
|
|
|
33
35
|
The plugin fetches from GitHub, registers the MCP server automatically, and makes all commands available immediately.
|
|
@@ -43,8 +45,8 @@ export BITBUCKET_API_TOKEN="your-atlassian-api-token"
|
|
|
43
45
|
export BITBUCKET_DEFAULT_WORKSPACE="your-default-workspace-slug"
|
|
44
46
|
```
|
|
45
47
|
|
|
46
|
-
To update: `claude plugin update
|
|
47
|
-
To uninstall: `claude plugin uninstall
|
|
48
|
+
To update: `claude plugin update ak`
|
|
49
|
+
To uninstall: `claude plugin uninstall ak`
|
|
48
50
|
|
|
49
51
|
---
|
|
50
52
|
|
|
@@ -65,7 +67,7 @@ npm run build
|
|
|
65
67
|
|
|
66
68
|
```bash
|
|
67
69
|
claude plugin marketplace add /absolute/path/to/agent-kit
|
|
68
|
-
claude plugin install
|
|
70
|
+
claude plugin install ak
|
|
69
71
|
```
|
|
70
72
|
|
|
71
73
|
This registers the MCP server automatically (pointing to your local build). Do **not** manually add a `kit-agents` entry to `settings.json` — the plugin handles that.
|
|
@@ -89,6 +91,16 @@ export BITBUCKET_DEFAULT_WORKSPACE="your-default-workspace-slug"
|
|
|
89
91
|
|
|
90
92
|
---
|
|
91
93
|
|
|
94
|
+
### Option 3: Install as Gemini Extension (Optional)
|
|
95
|
+
|
|
96
|
+
If you want to reuse the commands with [Gemini CLI](https://geminicli.com) (for `/ak:delegate` to Gemini), install it using:
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
gemini extension install https://github.com/hanh-nd/agent-kit
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
92
104
|
## Development
|
|
93
105
|
|
|
94
106
|
```bash
|
package/dist/kit-server.js
CHANGED
|
@@ -3223,8 +3223,8 @@ var require_utils = __commonJS({
|
|
|
3223
3223
|
}
|
|
3224
3224
|
return ind;
|
|
3225
3225
|
}
|
|
3226
|
-
function removeDotSegments(
|
|
3227
|
-
let input =
|
|
3226
|
+
function removeDotSegments(path4) {
|
|
3227
|
+
let input = path4;
|
|
3228
3228
|
const output = [];
|
|
3229
3229
|
let nextSlash = -1;
|
|
3230
3230
|
let len = 0;
|
|
@@ -3423,8 +3423,8 @@ var require_schemes = __commonJS({
|
|
|
3423
3423
|
wsComponent.secure = void 0;
|
|
3424
3424
|
}
|
|
3425
3425
|
if (wsComponent.resourceName) {
|
|
3426
|
-
const [
|
|
3427
|
-
wsComponent.path =
|
|
3426
|
+
const [path4, query] = wsComponent.resourceName.split("?");
|
|
3427
|
+
wsComponent.path = path4 && path4 !== "/" ? path4 : void 0;
|
|
3428
3428
|
wsComponent.query = query;
|
|
3429
3429
|
wsComponent.resourceName = void 0;
|
|
3430
3430
|
}
|
|
@@ -7277,8 +7277,8 @@ function getErrorMap() {
|
|
|
7277
7277
|
|
|
7278
7278
|
// node_modules/zod/v3/helpers/parseUtil.js
|
|
7279
7279
|
var makeIssue = (params) => {
|
|
7280
|
-
const { data, path:
|
|
7281
|
-
const fullPath = [...
|
|
7280
|
+
const { data, path: path4, errorMaps, issueData } = params;
|
|
7281
|
+
const fullPath = [...path4, ...issueData.path || []];
|
|
7282
7282
|
const fullIssue = {
|
|
7283
7283
|
...issueData,
|
|
7284
7284
|
path: fullPath
|
|
@@ -7394,11 +7394,11 @@ var errorUtil;
|
|
|
7394
7394
|
|
|
7395
7395
|
// node_modules/zod/v3/types.js
|
|
7396
7396
|
var ParseInputLazyPath = class {
|
|
7397
|
-
constructor(parent, value,
|
|
7397
|
+
constructor(parent, value, path4, key) {
|
|
7398
7398
|
this._cachedPath = [];
|
|
7399
7399
|
this.parent = parent;
|
|
7400
7400
|
this.data = value;
|
|
7401
|
-
this._path =
|
|
7401
|
+
this._path = path4;
|
|
7402
7402
|
this._key = key;
|
|
7403
7403
|
}
|
|
7404
7404
|
get path() {
|
|
@@ -11036,10 +11036,10 @@ function assignProp(target, prop, value) {
|
|
|
11036
11036
|
configurable: true
|
|
11037
11037
|
});
|
|
11038
11038
|
}
|
|
11039
|
-
function getElementAtPath(obj,
|
|
11040
|
-
if (!
|
|
11039
|
+
function getElementAtPath(obj, path4) {
|
|
11040
|
+
if (!path4)
|
|
11041
11041
|
return obj;
|
|
11042
|
-
return
|
|
11042
|
+
return path4.reduce((acc, key) => acc?.[key], obj);
|
|
11043
11043
|
}
|
|
11044
11044
|
function promiseAllObject(promisesObj) {
|
|
11045
11045
|
const keys = Object.keys(promisesObj);
|
|
@@ -11359,11 +11359,11 @@ function aborted(x, startIndex = 0) {
|
|
|
11359
11359
|
}
|
|
11360
11360
|
return false;
|
|
11361
11361
|
}
|
|
11362
|
-
function prefixIssues(
|
|
11362
|
+
function prefixIssues(path4, issues) {
|
|
11363
11363
|
return issues.map((iss) => {
|
|
11364
11364
|
var _a;
|
|
11365
11365
|
(_a = iss).path ?? (_a.path = []);
|
|
11366
|
-
iss.path.unshift(
|
|
11366
|
+
iss.path.unshift(path4);
|
|
11367
11367
|
return iss;
|
|
11368
11368
|
});
|
|
11369
11369
|
}
|
|
@@ -20991,55 +20991,15 @@ var StdioServerTransport = class {
|
|
|
20991
20991
|
|
|
20992
20992
|
// src/tools/core.ts
|
|
20993
20993
|
import * as fs from "fs";
|
|
20994
|
-
import * as
|
|
20994
|
+
import * as path from "path";
|
|
20995
20995
|
|
|
20996
20996
|
// src/utils.ts
|
|
20997
|
-
import path from "path";
|
|
20998
20997
|
function getWorkspaceRoot() {
|
|
20999
20998
|
return process.env.WORKSPACE_DIR || process.env.CLAUDE_WORKSPACE || process.env.GEMINI_WORKSPACE || process.env.INIT_CWD || process.env.PWD || process.cwd();
|
|
21000
20999
|
}
|
|
21001
|
-
function getExtensionRoot() {
|
|
21002
|
-
if (process.env.CLAUDE_PLUGIN_ROOT) {
|
|
21003
|
-
return process.env.CLAUDE_PLUGIN_ROOT;
|
|
21004
|
-
}
|
|
21005
|
-
const distDir = path.dirname(new URL(import.meta.url).pathname);
|
|
21006
|
-
return path.resolve(distDir, "..");
|
|
21007
|
-
}
|
|
21008
21000
|
|
|
21009
21001
|
// src/tools/core.ts
|
|
21010
21002
|
function registerCoreTools(server2) {
|
|
21011
|
-
server2.tool(
|
|
21012
|
-
"kit_get_extension_info",
|
|
21013
|
-
"Get absolute paths to the agent-kit extension directories (agents, skills, commands, scripts). Use the returned paths with the native Read tool to load agent personas and skill modules.",
|
|
21014
|
-
{},
|
|
21015
|
-
async () => {
|
|
21016
|
-
try {
|
|
21017
|
-
const root = getExtensionRoot();
|
|
21018
|
-
return {
|
|
21019
|
-
content: [
|
|
21020
|
-
{
|
|
21021
|
-
type: "text",
|
|
21022
|
-
text: JSON.stringify(
|
|
21023
|
-
{
|
|
21024
|
-
extensionRoot: root,
|
|
21025
|
-
agentsDir: path2.join(root, "agents"),
|
|
21026
|
-
skillsDir: path2.join(root, "skills"),
|
|
21027
|
-
commandsDir: path2.join(root, "commands"),
|
|
21028
|
-
scriptsDir: path2.join(root, "scripts")
|
|
21029
|
-
},
|
|
21030
|
-
null,
|
|
21031
|
-
2
|
|
21032
|
-
)
|
|
21033
|
-
}
|
|
21034
|
-
]
|
|
21035
|
-
};
|
|
21036
|
-
} catch (error2) {
|
|
21037
|
-
return {
|
|
21038
|
-
content: [{ type: "text", text: `Error getting extension info: ${error2}` }]
|
|
21039
|
-
};
|
|
21040
|
-
}
|
|
21041
|
-
}
|
|
21042
|
-
);
|
|
21043
21003
|
server2.tool(
|
|
21044
21004
|
"kit_save_handoff",
|
|
21045
21005
|
"Save a brainstorm, plan, or ticket handoff artifact to .agent-kit/handoffs/. Returns the saved file path to use in next-step instructions.",
|
|
@@ -21053,11 +21013,11 @@ function registerCoreTools(server2) {
|
|
|
21053
21013
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
|
21054
21014
|
const safeSlug = slug.replace(/[^a-zA-Z0-9-_]/g, "-").toLowerCase();
|
|
21055
21015
|
const filename = `${type}-${timestamp}-${safeSlug}.md`;
|
|
21056
|
-
const handoffDir =
|
|
21016
|
+
const handoffDir = path.join(getWorkspaceRoot(), ".agent-kit", "handoffs", `${type}s`);
|
|
21057
21017
|
if (!fs.existsSync(handoffDir)) {
|
|
21058
21018
|
fs.mkdirSync(handoffDir, { recursive: true });
|
|
21059
21019
|
}
|
|
21060
|
-
const filePath =
|
|
21020
|
+
const filePath = path.join(handoffDir, filename);
|
|
21061
21021
|
fs.writeFileSync(filePath, content, "utf8");
|
|
21062
21022
|
return {
|
|
21063
21023
|
content: [{ type: "text", text: `\u2705 Saved to: ${filePath}` }]
|
|
@@ -21075,20 +21035,37 @@ function registerCoreTools(server2) {
|
|
|
21075
21035
|
import { spawn } from "child_process";
|
|
21076
21036
|
import { randomUUID } from "crypto";
|
|
21077
21037
|
import * as fs2 from "fs";
|
|
21078
|
-
import * as
|
|
21038
|
+
import * as path3 from "path";
|
|
21079
21039
|
|
|
21080
21040
|
// src/tools/security.ts
|
|
21081
21041
|
import { execFileSync } from "child_process";
|
|
21082
|
-
import * as
|
|
21042
|
+
import * as path2 from "path";
|
|
21083
21043
|
function sanitize(input) {
|
|
21084
21044
|
return String(input).replace(/[;&|`$<>\\]/g, "").trim().slice(0, 500);
|
|
21085
21045
|
}
|
|
21046
|
+
var FORBIDDEN_FILES = [
|
|
21047
|
+
".env",
|
|
21048
|
+
".bashrc",
|
|
21049
|
+
".zshrc",
|
|
21050
|
+
".profile",
|
|
21051
|
+
".bash_profile",
|
|
21052
|
+
".bash_history",
|
|
21053
|
+
".zsh_history",
|
|
21054
|
+
"config.mjs"
|
|
21055
|
+
// Some projects use this for sensitive config
|
|
21056
|
+
];
|
|
21086
21057
|
function validatePath(filePath, baseDir = process.cwd()) {
|
|
21087
|
-
const resolved =
|
|
21088
|
-
const root =
|
|
21089
|
-
if (resolved !== root && !resolved.startsWith(root +
|
|
21058
|
+
const resolved = path2.resolve(baseDir, filePath);
|
|
21059
|
+
const root = path2.resolve(baseDir);
|
|
21060
|
+
if (resolved !== root && !resolved.startsWith(root + path2.sep)) {
|
|
21090
21061
|
throw new Error(`Path traversal detected: ${filePath}`);
|
|
21091
21062
|
}
|
|
21063
|
+
const fileName = path2.basename(resolved);
|
|
21064
|
+
if (FORBIDDEN_FILES.some(
|
|
21065
|
+
(forbidden) => fileName === forbidden || fileName.startsWith(forbidden + ".")
|
|
21066
|
+
)) {
|
|
21067
|
+
throw new Error(`Access to sensitive file is FORBIDDEN: ${fileName}`);
|
|
21068
|
+
}
|
|
21092
21069
|
return resolved;
|
|
21093
21070
|
}
|
|
21094
21071
|
var GIT_TIMEOUT = parseInt(process.env.GEMINI_KIT_GIT_TIMEOUT || "30000", 10);
|
|
@@ -21114,9 +21091,9 @@ function taskSlug(task) {
|
|
|
21114
21091
|
return task.toLowerCase().replace(/[^a-z0-9\s]/g, "").trim().split(/\s+/).slice(0, 6).join("-").slice(0, 48);
|
|
21115
21092
|
}
|
|
21116
21093
|
function initAgentLog(workspaceRoot, slug, agent) {
|
|
21117
|
-
const logsDir =
|
|
21094
|
+
const logsDir = path3.join(workspaceRoot, ".agent-kit", "logs", "agents");
|
|
21118
21095
|
fs2.mkdirSync(logsDir, { recursive: true });
|
|
21119
|
-
const logFile =
|
|
21096
|
+
const logFile = path3.join(logsDir, `delegate-${slug}.log`);
|
|
21120
21097
|
const header = `Agent: ${agent}
|
|
21121
21098
|
Date: ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
21122
21099
|
${"\u2500".repeat(60)}
|
|
@@ -21126,7 +21103,7 @@ ${"\u2500".repeat(60)}
|
|
|
21126
21103
|
const logStream = fs2.createWriteStream(logFile, { flags: "a" });
|
|
21127
21104
|
logStream.on("error", () => {
|
|
21128
21105
|
});
|
|
21129
|
-
return { logPath:
|
|
21106
|
+
return { logPath: path3.relative(workspaceRoot, logFile), logStream };
|
|
21130
21107
|
}
|
|
21131
21108
|
function registerAgentTools(server2) {
|
|
21132
21109
|
server2.tool(
|
|
@@ -21142,7 +21119,7 @@ function registerAgentTools(server2) {
|
|
|
21142
21119
|
try {
|
|
21143
21120
|
const workspaceRoot = getWorkspaceRoot();
|
|
21144
21121
|
let prompt = task;
|
|
21145
|
-
const resolvedTask =
|
|
21122
|
+
const resolvedTask = path3.resolve(workspaceRoot, task);
|
|
21146
21123
|
if (fs2.existsSync(resolvedTask) && fs2.statSync(resolvedTask).isFile()) {
|
|
21147
21124
|
try {
|
|
21148
21125
|
validatePath(task, workspaceRoot);
|
package/dist/tools/core.js
CHANGED
|
@@ -4,41 +4,12 @@
|
|
|
4
4
|
import * as fs from 'fs';
|
|
5
5
|
import * as path from 'path';
|
|
6
6
|
import { z } from 'zod';
|
|
7
|
-
import {
|
|
7
|
+
import { getWorkspaceRoot } from '../utils.js';
|
|
8
8
|
import { DEFAULT_EXTENSIONS } from './config.js';
|
|
9
9
|
/**
|
|
10
10
|
* Register core tools with MCP server
|
|
11
11
|
*/
|
|
12
12
|
export function registerCoreTools(server) {
|
|
13
|
-
// ═══════════════════════════════════════════════════════════════
|
|
14
|
-
// TOOL: GET EXTENSION INFO
|
|
15
|
-
// Returns absolute paths so agents can use native Read tool
|
|
16
|
-
// for loading agent personas and skill files
|
|
17
|
-
// ═══════════════════════════════════════════════════════════════
|
|
18
|
-
server.tool('kit_get_extension_info', 'Get absolute paths to the agent-kit extension directories (agents, skills, commands, scripts). Use the returned paths with the native Read tool to load agent personas and skill modules.', {}, async () => {
|
|
19
|
-
try {
|
|
20
|
-
const root = getExtensionRoot();
|
|
21
|
-
return {
|
|
22
|
-
content: [
|
|
23
|
-
{
|
|
24
|
-
type: 'text',
|
|
25
|
-
text: JSON.stringify({
|
|
26
|
-
extensionRoot: root,
|
|
27
|
-
agentsDir: path.join(root, 'agents'),
|
|
28
|
-
skillsDir: path.join(root, 'skills'),
|
|
29
|
-
commandsDir: path.join(root, 'commands'),
|
|
30
|
-
scriptsDir: path.join(root, 'scripts'),
|
|
31
|
-
}, null, 2),
|
|
32
|
-
},
|
|
33
|
-
],
|
|
34
|
-
};
|
|
35
|
-
}
|
|
36
|
-
catch (error) {
|
|
37
|
-
return {
|
|
38
|
-
content: [{ type: 'text', text: `Error getting extension info: ${error}` }],
|
|
39
|
-
};
|
|
40
|
-
}
|
|
41
|
-
});
|
|
42
13
|
// ═══════════════════════════════════════════════════════════════
|
|
43
14
|
// TOOL: SAVE HANDOFF
|
|
44
15
|
// Writes brainstorm/plan/ticket artifacts to the workspace
|
package/dist/tools/security.d.ts
CHANGED
|
@@ -14,9 +14,8 @@
|
|
|
14
14
|
*/
|
|
15
15
|
export declare function sanitize(input: string): string;
|
|
16
16
|
/**
|
|
17
|
-
* Validate file path to prevent path traversal attacks
|
|
18
|
-
* Uses stricter path.sep check to prevent prefix matching flaws
|
|
19
|
-
* (e.g., /.geminit-kit/handoffs/app should not match /.geminit-kit/handoffs/app-secret)
|
|
17
|
+
* Validate file path to prevent path traversal attacks and access to forbidden files.
|
|
18
|
+
* Uses stricter path.sep check to prevent prefix matching flaws.
|
|
20
19
|
*/
|
|
21
20
|
export declare function validatePath(filePath: string, baseDir?: string): string;
|
|
22
21
|
/**
|
package/dist/tools/security.js
CHANGED
|
@@ -25,17 +25,35 @@ export function sanitize(input) {
|
|
|
25
25
|
.slice(0, 500); // Limit length
|
|
26
26
|
}
|
|
27
27
|
/**
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
|
|
28
|
+
* Sensitive files that agents are FORBIDDEN from reading/writing.
|
|
29
|
+
* Includes environment variables, shell configs, and system profiles.
|
|
30
|
+
*/
|
|
31
|
+
const FORBIDDEN_FILES = [
|
|
32
|
+
'.env',
|
|
33
|
+
'.bashrc',
|
|
34
|
+
'.zshrc',
|
|
35
|
+
'.profile',
|
|
36
|
+
'.bash_profile',
|
|
37
|
+
'.bash_history',
|
|
38
|
+
'.zsh_history',
|
|
39
|
+
'config.mjs', // Some projects use this for sensitive config
|
|
40
|
+
];
|
|
41
|
+
/**
|
|
42
|
+
* Validate file path to prevent path traversal attacks and access to forbidden files.
|
|
43
|
+
* Uses stricter path.sep check to prevent prefix matching flaws.
|
|
31
44
|
*/
|
|
32
45
|
export function validatePath(filePath, baseDir = process.cwd()) {
|
|
33
46
|
const resolved = path.resolve(baseDir, filePath);
|
|
34
47
|
const root = path.resolve(baseDir);
|
|
35
|
-
//
|
|
48
|
+
// 1. Path traversal check
|
|
36
49
|
if (resolved !== root && !resolved.startsWith(root + path.sep)) {
|
|
37
50
|
throw new Error(`Path traversal detected: ${filePath}`);
|
|
38
51
|
}
|
|
52
|
+
// 2. Forbidden files check (exact filename or suffix)
|
|
53
|
+
const fileName = path.basename(resolved);
|
|
54
|
+
if (FORBIDDEN_FILES.some((forbidden) => fileName === forbidden || fileName.startsWith(forbidden + '.'))) {
|
|
55
|
+
throw new Error(`Access to sensitive file is FORBIDDEN: ${fileName}`);
|
|
56
|
+
}
|
|
39
57
|
return resolved;
|
|
40
58
|
}
|
|
41
59
|
/**
|
package/dist/utils.d.ts
CHANGED
|
@@ -2,9 +2,3 @@
|
|
|
2
2
|
* Utility functions for agent-kit
|
|
3
3
|
*/
|
|
4
4
|
export declare function getWorkspaceRoot(): string;
|
|
5
|
-
/**
|
|
6
|
-
* Resolve path to plugin root.
|
|
7
|
-
* When installed as a Claude Code plugin, CLAUDE_PLUGIN_ROOT is set automatically.
|
|
8
|
-
* Falls back to resolving from the bundled file location for local development.
|
|
9
|
-
*/
|
|
10
|
-
export declare function getExtensionRoot(): string;
|
package/dist/utils.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Utility functions for agent-kit
|
|
3
3
|
*/
|
|
4
|
-
import path from 'path';
|
|
5
4
|
export function getWorkspaceRoot() {
|
|
6
5
|
return (process.env.WORKSPACE_DIR ||
|
|
7
6
|
process.env.CLAUDE_WORKSPACE ||
|
|
@@ -10,17 +9,3 @@ export function getWorkspaceRoot() {
|
|
|
10
9
|
process.env.PWD ||
|
|
11
10
|
process.cwd());
|
|
12
11
|
}
|
|
13
|
-
/**
|
|
14
|
-
* Resolve path to plugin root.
|
|
15
|
-
* When installed as a Claude Code plugin, CLAUDE_PLUGIN_ROOT is set automatically.
|
|
16
|
-
* Falls back to resolving from the bundled file location for local development.
|
|
17
|
-
*/
|
|
18
|
-
export function getExtensionRoot() {
|
|
19
|
-
// CLAUDE_PLUGIN_ROOT is set by Claude Code when plugin is installed
|
|
20
|
-
if (process.env.CLAUDE_PLUGIN_ROOT) {
|
|
21
|
-
return process.env.CLAUDE_PLUGIN_ROOT;
|
|
22
|
-
}
|
|
23
|
-
// Fallback: resolve from bundled file location (dist/kit-server.js → ../)
|
|
24
|
-
const distDir = path.dirname(new URL(import.meta.url).pathname);
|
|
25
|
-
return path.resolve(distDir, '..');
|
|
26
|
-
}
|