@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 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 agent-kit
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 agent-kit`
47
- To uninstall: `claude plugin uninstall agent-kit`
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 agent-kit
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
@@ -3223,8 +3223,8 @@ var require_utils = __commonJS({
3223
3223
  }
3224
3224
  return ind;
3225
3225
  }
3226
- function removeDotSegments(path5) {
3227
- let input = path5;
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 [path5, query] = wsComponent.resourceName.split("?");
3427
- wsComponent.path = path5 && path5 !== "/" ? path5 : void 0;
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: path5, errorMaps, issueData } = params;
7281
- const fullPath = [...path5, ...issueData.path || []];
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, path5, key) {
7397
+ constructor(parent, value, path4, key) {
7398
7398
  this._cachedPath = [];
7399
7399
  this.parent = parent;
7400
7400
  this.data = value;
7401
- this._path = path5;
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, path5) {
11040
- if (!path5)
11039
+ function getElementAtPath(obj, path4) {
11040
+ if (!path4)
11041
11041
  return obj;
11042
- return path5.reduce((acc, key) => acc?.[key], obj);
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(path5, issues) {
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(path5);
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 path2 from "path";
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 = path2.join(getWorkspaceRoot(), ".agent-kit", "handoffs", `${type}s`);
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 = path2.join(handoffDir, filename);
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 path4 from "path";
21038
+ import * as path3 from "path";
21079
21039
 
21080
21040
  // src/tools/security.ts
21081
21041
  import { execFileSync } from "child_process";
21082
- import * as path3 from "path";
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 = path3.resolve(baseDir, filePath);
21088
- const root = path3.resolve(baseDir);
21089
- if (resolved !== root && !resolved.startsWith(root + path3.sep)) {
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 = path4.join(workspaceRoot, ".agent-kit", "logs", "agents");
21094
+ const logsDir = path3.join(workspaceRoot, ".agent-kit", "logs", "agents");
21118
21095
  fs2.mkdirSync(logsDir, { recursive: true });
21119
- const logFile = path4.join(logsDir, `delegate-${slug}.log`);
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: path4.relative(workspaceRoot, logFile), logStream };
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 = path4.resolve(workspaceRoot, task);
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);
@@ -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 { getExtensionRoot, getWorkspaceRoot } from '../utils.js';
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
@@ -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
  /**
@@ -25,17 +25,35 @@ export function sanitize(input) {
25
25
  .slice(0, 500); // Limit length
26
26
  }
27
27
  /**
28
- * Validate file path to prevent path traversal attacks
29
- * Uses stricter path.sep check to prevent prefix matching flaws
30
- * (e.g., /.geminit-kit/handoffs/app should not match /.geminit-kit/handoffs/app-secret)
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
- // Stricter: exact match OR starts with root + separator
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
- }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hanhnd/agent-kit",
3
- "version": "1.0.5",
3
+ "version": "1.0.7",
4
4
  "description": "Super Engineer - Team of AI Agents for software development (Claude Code Plugin)",
5
5
  "type": "module",
6
6
  "main": "dist/kit-server.js",