@hiro-c/agent-gate 1.1.0 → 1.2.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/README.md +1 -0
- package/dist/config/Config.d.ts +9 -0
- package/dist/config/Config.d.ts.map +1 -1
- package/dist/config/Config.js +5 -0
- package/dist/deterministic/engine.d.ts +13 -1
- package/dist/deterministic/engine.d.ts.map +1 -1
- package/dist/deterministic/engine.js +17 -2
- package/dist/hooks/processHookData.d.ts.map +1 -1
- package/dist/hooks/processHookData.js +4 -2
- package/dist/validation/validator.d.ts +5 -1
- package/dist/validation/validator.d.ts.map +1 -1
- package/dist/validation/validator.js +7 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -88,6 +88,7 @@ Full options: see [docs/config.md](docs/config.md) (TODO) or `AgentGatePluginCon
|
|
|
88
88
|
| `AGENT_GATE_LOG` | `1` writes decisions to `~/.agent-gate/log.jsonl` |
|
|
89
89
|
| `AGENT_GATE_API_KEY` | Use Anthropic API directly instead of `claude` CLI |
|
|
90
90
|
| `AGENT_GATE_USE_SDK` | `1` prefers the Anthropic agent SDK over API/CLI (no API key needed; works best with daemon mode) |
|
|
91
|
+
| `AGENT_GATE_ON_ERROR` | `block` to fail-closed when a rule or AI client throws (default `allow`) |
|
|
91
92
|
| `AGENT_GATE_DAEMON` | `1` routes through the daemon if it is running |
|
|
92
93
|
|
|
93
94
|
## Supported AI tools
|
package/dist/config/Config.d.ts
CHANGED
|
@@ -19,6 +19,14 @@ export type ConfigOptions = {
|
|
|
19
19
|
* cost, so this is most useful when paired with `agent-gate daemon`.
|
|
20
20
|
*/
|
|
21
21
|
useSdk?: boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Behavior when a rule or model client throws unexpectedly.
|
|
24
|
+
* - "allow" (default): swallow the error and let the operation through.
|
|
25
|
+
* Optimized for developer ergonomics so flaky AI does not block work.
|
|
26
|
+
* - "block": turn the error into a block verdict. Recommended for
|
|
27
|
+
* production / enterprise pipelines that prefer fail-closed safety.
|
|
28
|
+
*/
|
|
29
|
+
onError?: 'allow' | 'block';
|
|
22
30
|
};
|
|
23
31
|
export declare class Config {
|
|
24
32
|
readonly model: string;
|
|
@@ -28,6 +36,7 @@ export declare class Config {
|
|
|
28
36
|
readonly useSystemClaude: boolean;
|
|
29
37
|
readonly reasonLang: string | undefined;
|
|
30
38
|
readonly useSdk: boolean;
|
|
39
|
+
readonly onError: 'allow' | 'block';
|
|
31
40
|
constructor(options?: ConfigOptions);
|
|
32
41
|
get useApi(): boolean;
|
|
33
42
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Config.d.ts","sourceRoot":"","sources":["../../src/config/Config.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,aAAa,sBAAsB,CAAA;AAEhD,MAAM,MAAM,aAAa,GAAG;IAC1B,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,eAAe,CAAC,EAAE,OAAO,CAAA;IACzB;;;;;OAKG;IACH,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB;;;;;OAKG;IACH,MAAM,CAAC,EAAE,OAAO,CAAA;
|
|
1
|
+
{"version":3,"file":"Config.d.ts","sourceRoot":"","sources":["../../src/config/Config.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,aAAa,sBAAsB,CAAA;AAEhD,MAAM,MAAM,aAAa,GAAG;IAC1B,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,eAAe,CAAC,EAAE,OAAO,CAAA;IACzB;;;;;OAKG;IACH,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB;;;;;OAKG;IACH,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,OAAO,GAAG,OAAO,CAAA;CAC5B,CAAA;AAED,qBAAa,MAAM;IACjB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAA;IACnC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAA;IAC1B,QAAQ,CAAC,eAAe,EAAE,OAAO,CAAA;IACjC,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,SAAS,CAAA;IACvC,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAA;IACxB,QAAQ,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAA;gBAEvB,OAAO,CAAC,EAAE,aAAa;IAenC,IAAI,MAAM,IAAI,OAAO,CAEpB;CACF"}
|
package/dist/config/Config.js
CHANGED
|
@@ -10,6 +10,7 @@ class Config {
|
|
|
10
10
|
useSystemClaude;
|
|
11
11
|
reasonLang;
|
|
12
12
|
useSdk;
|
|
13
|
+
onError;
|
|
13
14
|
constructor(options) {
|
|
14
15
|
this.model = options?.model ?? process.env.AGENT_GATE_MODEL ?? exports.DEFAULT_MODEL;
|
|
15
16
|
this.apiKey = options?.apiKey ?? process.env.AGENT_GATE_API_KEY;
|
|
@@ -19,6 +20,10 @@ class Config {
|
|
|
19
20
|
this.useSystemClaude = options?.useSystemClaude ?? process.env.USE_SYSTEM_CLAUDE === 'true';
|
|
20
21
|
this.reasonLang = options?.reasonLang ?? process.env.AGENT_GATE_REASON_LANG;
|
|
21
22
|
this.useSdk = options?.useSdk ?? process.env.AGENT_GATE_USE_SDK === '1';
|
|
23
|
+
const envOnError = process.env.AGENT_GATE_ON_ERROR;
|
|
24
|
+
this.onError =
|
|
25
|
+
options?.onError ??
|
|
26
|
+
(envOnError === 'block' ? 'block' : 'allow');
|
|
22
27
|
}
|
|
23
28
|
get useApi() {
|
|
24
29
|
return this.apiKey !== undefined && this.apiKey !== '';
|
|
@@ -7,5 +7,17 @@ export type EngineResult = {
|
|
|
7
7
|
reason: string;
|
|
8
8
|
ruleId: string;
|
|
9
9
|
};
|
|
10
|
-
export
|
|
10
|
+
export type OnErrorPolicy = 'allow' | 'block';
|
|
11
|
+
export interface RunDeterministicRulesOptions {
|
|
12
|
+
/**
|
|
13
|
+
* What to do when a rule's check function throws.
|
|
14
|
+
* - "allow" (default): swallow the exception and continue with the
|
|
15
|
+
* next rule. Compatible with prior versions.
|
|
16
|
+
* - "block": turn the exception into a block verdict and stop the
|
|
17
|
+
* engine. Suitable for enterprise / production use where the
|
|
18
|
+
* safest response to a misbehaving rule is to halt.
|
|
19
|
+
*/
|
|
20
|
+
onError?: OnErrorPolicy;
|
|
21
|
+
}
|
|
22
|
+
export declare function runDeterministicRules(toolName: string, toolInput: Record<string, unknown>, rules: DeterministicRule[], ctx?: SessionContext, options?: RunDeterministicRulesOptions): EngineResult;
|
|
11
23
|
//# sourceMappingURL=engine.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../../src/deterministic/engine.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAA;AAC3C,OAAO,EAAE,cAAc,EAAE,MAAM,mCAAmC,CAAA;AAElE,MAAM,MAAM,YAAY,GACpB;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,GACjB;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAA;AAErD,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAClC,KAAK,EAAE,iBAAiB,EAAE,EAC1B,GAAG,CAAC,EAAE,cAAc,
|
|
1
|
+
{"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../../src/deterministic/engine.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAA;AAC3C,OAAO,EAAE,cAAc,EAAE,MAAM,mCAAmC,CAAA;AAElE,MAAM,MAAM,YAAY,GACpB;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,GACjB;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAA;AAErD,MAAM,MAAM,aAAa,GAAG,OAAO,GAAG,OAAO,CAAA;AAE7C,MAAM,WAAW,4BAA4B;IAC3C;;;;;;;OAOG;IACH,OAAO,CAAC,EAAE,aAAa,CAAA;CACxB;AAED,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAClC,KAAK,EAAE,iBAAiB,EAAE,EAC1B,GAAG,CAAC,EAAE,cAAc,EACpB,OAAO,CAAC,EAAE,4BAA4B,GACrC,YAAY,CAuBd"}
|
|
@@ -1,9 +1,24 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.runDeterministicRules = runDeterministicRules;
|
|
4
|
-
function runDeterministicRules(toolName, toolInput, rules, ctx) {
|
|
4
|
+
function runDeterministicRules(toolName, toolInput, rules, ctx, options) {
|
|
5
|
+
const onError = options?.onError ?? 'allow';
|
|
5
6
|
for (const rule of rules) {
|
|
6
|
-
|
|
7
|
+
let verdict;
|
|
8
|
+
try {
|
|
9
|
+
verdict = rule.check(toolName, toolInput, ctx);
|
|
10
|
+
}
|
|
11
|
+
catch (e) {
|
|
12
|
+
if (onError === 'block') {
|
|
13
|
+
const reason = e instanceof Error ? e.message : String(e);
|
|
14
|
+
return {
|
|
15
|
+
kind: 'block',
|
|
16
|
+
reason: `Rule "${rule.id}" failed: ${reason}. Failing closed.`,
|
|
17
|
+
ruleId: rule.id,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
7
22
|
if (verdict.kind === 'block') {
|
|
8
23
|
return { kind: 'block', reason: verdict.reason, ruleId: rule.id };
|
|
9
24
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"processHookData.d.ts","sourceRoot":"","sources":["../../src/hooks/processHookData.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,gBAAgB,EAAE,MAAM,qCAAqC,CAAA;AACtE,OAAO,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAA;AAC1D,OAAO,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAA;AAE7D,OAAO,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAA;AACnD,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAA;AAKzC,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAA;AAG1D,OAAO,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAE7C,OAAO,EACL,eAAe,EAEhB,MAAM,2BAA2B,CAAA;AAMlC,OAAO,EAAE,QAAQ,EAAE,MAAM,2BAA2B,CAAA;AAWpD,MAAM,WAAW,aAAa;IAC5B,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAA;IAChC,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;CAC7C;AA4BD,MAAM,WAAW,mBAAmB;IAClC,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,eAAe,CAAC,EAAE,eAAe,CAAA;IACjC,SAAS,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,UAAU,EAAE,CAAA;IACzC,WAAW,CAAC,EAAE,OAAO,SAAS,CAAA;IAC9B,cAAc,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,YAAY,CAAA;IACjD,aAAa,CAAC,EAAE,aAAa,CAAA;IAC7B,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,kBAAkB,CAAC,EAAE,iBAAiB,EAAE,CAAA;IACxC,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,QAAQ,CAAA;IACnB,wEAAwE;IACxE,OAAO,CAAC,EAAE;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;CAC9C;AAaD,wBAAsB,eAAe,CACnC,KAAK,EAAE,MAAM,EACb,IAAI,CAAC,EAAE,mBAAmB,GACzB,OAAO,CAAC,gBAAgB,CAAC,
|
|
1
|
+
{"version":3,"file":"processHookData.d.ts","sourceRoot":"","sources":["../../src/hooks/processHookData.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,gBAAgB,EAAE,MAAM,qCAAqC,CAAA;AACtE,OAAO,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAA;AAC1D,OAAO,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAA;AAE7D,OAAO,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAA;AACnD,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAA;AAKzC,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAA;AAG1D,OAAO,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAE7C,OAAO,EACL,eAAe,EAEhB,MAAM,2BAA2B,CAAA;AAMlC,OAAO,EAAE,QAAQ,EAAE,MAAM,2BAA2B,CAAA;AAWpD,MAAM,WAAW,aAAa;IAC5B,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAA;IAChC,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;CAC7C;AA4BD,MAAM,WAAW,mBAAmB;IAClC,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,eAAe,CAAC,EAAE,eAAe,CAAA;IACjC,SAAS,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,UAAU,EAAE,CAAA;IACzC,WAAW,CAAC,EAAE,OAAO,SAAS,CAAA;IAC9B,cAAc,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,YAAY,CAAA;IACjD,aAAa,CAAC,EAAE,aAAa,CAAA;IAC7B,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,kBAAkB,CAAC,EAAE,iBAAiB,EAAE,CAAA;IACxC,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,QAAQ,CAAA;IACnB,wEAAwE;IACxE,OAAO,CAAC,EAAE;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;CAC9C;AAaD,wBAAsB,eAAe,CACnC,KAAK,EAAE,MAAM,EACb,IAAI,CAAC,EAAE,mBAAmB,GACzB,OAAO,CAAC,gBAAgB,CAAC,CAuI3B"}
|
|
@@ -89,7 +89,7 @@ async function processHookData(input, deps) {
|
|
|
89
89
|
// any cooldown or AI check.
|
|
90
90
|
const deterministicRules = deps?.deterministicRules ??
|
|
91
91
|
(0, defaultRules_1.buildDefaultDeterministicRules)(agentGateConfig);
|
|
92
|
-
const ruleVerdict = (0, engine_1.runDeterministicRules)(toolName, toolInput, deterministicRules, sessionContext);
|
|
92
|
+
const ruleVerdict = (0, engine_1.runDeterministicRules)(toolName, toolInput, deterministicRules, sessionContext, { onError: config.onError });
|
|
93
93
|
if (ruleVerdict.kind === 'block') {
|
|
94
94
|
bus.emit({
|
|
95
95
|
type: 'rule.fired',
|
|
@@ -139,7 +139,9 @@ async function processHookData(input, deps) {
|
|
|
139
139
|
rulesCount: rules.length,
|
|
140
140
|
});
|
|
141
141
|
const aiStart = Date.now();
|
|
142
|
-
const result = await validate(rules, toolName, toolInput, modelClient
|
|
142
|
+
const result = await validate(rules, toolName, toolInput, modelClient, {
|
|
143
|
+
onError: config.onError,
|
|
144
|
+
});
|
|
143
145
|
const aiLatency = Date.now() - aiStart;
|
|
144
146
|
// Update cooldown timestamp AFTER successful validation
|
|
145
147
|
if (cooldownStore) {
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { ValidationResult } from '../contracts/types/ValidationResult';
|
|
2
2
|
import { RuleSource } from '../contracts/types/RuleSource';
|
|
3
3
|
import { IModelClient } from '../contracts/types/ModelClient';
|
|
4
|
-
export
|
|
4
|
+
export interface ValidatorOptions {
|
|
5
|
+
/** Fail-closed behavior on model client failures. Default: "allow". */
|
|
6
|
+
onError?: 'allow' | 'block';
|
|
7
|
+
}
|
|
8
|
+
export declare function validator(rules: RuleSource[], toolName: string, toolInput: Record<string, unknown>, modelClient: IModelClient, options?: ValidatorOptions): Promise<ValidationResult>;
|
|
5
9
|
//# sourceMappingURL=validator.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validator.d.ts","sourceRoot":"","sources":["../../src/validation/validator.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,qCAAqC,CAAA;AACtE,OAAO,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAA;AAC1D,OAAO,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAA;AAQ7D,wBAAsB,SAAS,CAC7B,KAAK,EAAE,UAAU,EAAE,EACnB,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAClC,WAAW,EAAE,YAAY,
|
|
1
|
+
{"version":3,"file":"validator.d.ts","sourceRoot":"","sources":["../../src/validation/validator.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,qCAAqC,CAAA;AACtE,OAAO,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAA;AAC1D,OAAO,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAA;AAQ7D,MAAM,WAAW,gBAAgB;IAC/B,uEAAuE;IACvE,OAAO,CAAC,EAAE,OAAO,GAAG,OAAO,CAAA;CAC5B;AAED,wBAAsB,SAAS,CAC7B,KAAK,EAAE,UAAU,EAAE,EACnB,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAClC,WAAW,EAAE,YAAY,EACzB,OAAO,CAAC,EAAE,gBAAgB,GACzB,OAAO,CAAC,gBAAgB,CAAC,CAmB3B"}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.validator = validator;
|
|
4
4
|
const context_1 = require("./prompts/context");
|
|
5
|
-
async function validator(rules, toolName, toolInput, modelClient) {
|
|
5
|
+
async function validator(rules, toolName, toolInput, modelClient, options) {
|
|
6
6
|
try {
|
|
7
7
|
const prompt = (0, context_1.buildPrompt)(rules, toolName, toolInput);
|
|
8
8
|
const response = await modelClient.ask(prompt);
|
|
@@ -10,6 +10,12 @@ async function validator(rules, toolName, toolInput, modelClient) {
|
|
|
10
10
|
}
|
|
11
11
|
catch (error) {
|
|
12
12
|
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
13
|
+
if (options?.onError === 'block') {
|
|
14
|
+
return {
|
|
15
|
+
decision: 'block',
|
|
16
|
+
reason: `Validation failed (failing closed): ${errorMessage}`,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
13
19
|
return {
|
|
14
20
|
decision: undefined,
|
|
15
21
|
reason: `Validation error (allowing operation): ${errorMessage}`,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hiro-c/agent-gate",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "Runtime rule enforcer for AI coding agents. Reads CLAUDE.md / AGENTS.md / .cursorrules and enforces them via Claude Code and Cursor hooks, with a deterministic safety baseline.",
|
|
5
5
|
"author": "Hiro-Chiba",
|
|
6
6
|
"license": "MIT",
|