@kurrent/kcap 0.9.0 → 0.9.2
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/bin/kcap.js
CHANGED
|
@@ -28,73 +28,98 @@ const PLATFORM_PACKAGES = {
|
|
|
28
28
|
"win32-x64": "@kurrent/kcap-win-x64",
|
|
29
29
|
};
|
|
30
30
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
31
|
+
// Resolves the npm dist-tag to install for `kcap update`, based on the
|
|
32
|
+
// resolved channel reported by `kcap update --check`'s `install_tag` field.
|
|
33
|
+
// Falls back to "latest" when the probe is missing, failed, or has no tag,
|
|
34
|
+
// preserving today's default behavior.
|
|
35
|
+
function resolveInstallSpec(info) {
|
|
36
|
+
const tag = info && typeof info.install_tag === "string" && info.install_tag
|
|
37
|
+
? info.install_tag
|
|
38
|
+
: "latest";
|
|
39
|
+
return `@kurrent/kcap@${tag}`;
|
|
39
40
|
}
|
|
40
41
|
|
|
41
|
-
//
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
process.exit(1);
|
|
42
|
+
// Builds the arg list for the `kcap update --check` probe, forwarding only
|
|
43
|
+
// the channel-switch flags (`--beta`/`--stable`) from the user's `kcap update`
|
|
44
|
+
// invocation. Nothing else is forwarded — the probe already supplies `--check`
|
|
45
|
+
// and `--no-update-check` itself, so this is a controlled call.
|
|
46
|
+
function probeArgs(updArgs) {
|
|
47
|
+
const channelFlags = (updArgs || []).filter((a) => a === "--beta" || a === "--stable");
|
|
48
|
+
return ["update", "--check", "--no-update-check", ...channelFlags];
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
|
|
52
|
-
|
|
51
|
+
// Everything below actually DOES something (resolves the binary, execs it,
|
|
52
|
+
// runs the update flow), so it's guarded to only run when this file is
|
|
53
|
+
// executed directly — not when `require()`d (e.g. by the test).
|
|
54
|
+
if (require.main === module) {
|
|
55
|
+
const musl = process.platform === "linux" && isMusl() ? "-musl" : "";
|
|
56
|
+
const platformKey = `${process.platform}${musl}-${process.arch}`;
|
|
57
|
+
const packageName = PLATFORM_PACKAGES[platformKey];
|
|
58
|
+
|
|
59
|
+
if (!packageName) {
|
|
60
|
+
console.error(`Unsupported platform: ${platformKey}`);
|
|
61
|
+
console.error(`Supported: ${Object.keys(PLATFORM_PACKAGES).join(", ")}`);
|
|
62
|
+
process.exit(1);
|
|
63
|
+
}
|
|
53
64
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
}
|
|
65
|
+
// Resolve the platform package
|
|
66
|
+
let binaryDir;
|
|
67
|
+
try {
|
|
68
|
+
binaryDir = path.dirname(require.resolve(`${packageName}/package.json`));
|
|
69
|
+
} catch {
|
|
70
|
+
console.error(`Platform package ${packageName} is not installed.`);
|
|
71
|
+
console.error(`Try: npm install -g @kurrent/kcap`);
|
|
72
|
+
process.exit(1);
|
|
73
|
+
}
|
|
58
74
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
fs.accessSync(binaryPath, fs.constants.X_OK);
|
|
62
|
-
} catch {
|
|
63
|
-
try { fs.chmodSync(binaryPath, 0o755); } catch {}
|
|
64
|
-
}
|
|
75
|
+
const ext = process.platform === "win32" ? ".exe" : "";
|
|
76
|
+
const binaryPath = path.join(binaryDir, "bin", `kcap${ext}`);
|
|
65
77
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
// from this script (with the native binary not running) npm can overwrite the
|
|
70
|
-
// binary even on Windows, with no temp-file/rename/detached-helper dance.
|
|
71
|
-
// `--check` (JSON probe) and `--help` fall through to the native binary.
|
|
72
|
-
{
|
|
73
|
-
const updArgs = process.argv.slice(3);
|
|
74
|
-
const checkOnly = updArgs.includes("--check");
|
|
75
|
-
const wantsHelp = updArgs.some((a) => a === "--help" || a === "-h");
|
|
76
|
-
if (process.argv[2] === "update" && !checkOnly && !wantsHelp) {
|
|
77
|
-
runUpdate(binaryPath); // never returns
|
|
78
|
+
if (!fs.existsSync(binaryPath)) {
|
|
79
|
+
console.error(`Binary not found at ${binaryPath}`);
|
|
80
|
+
process.exit(1);
|
|
78
81
|
}
|
|
79
|
-
}
|
|
80
82
|
|
|
81
|
-
//
|
|
82
|
-
try {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
83
|
+
// Ensure the binary is executable (npm doesn't always preserve permissions)
|
|
84
|
+
try {
|
|
85
|
+
fs.accessSync(binaryPath, fs.constants.X_OK);
|
|
86
|
+
} catch {
|
|
87
|
+
try { fs.chmodSync(binaryPath, 0o755); } catch {}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// `kcap update` for npm-global installs is driven HERE, in the Node launcher,
|
|
91
|
+
// not by the native binary. The OS locks an executable image for the whole
|
|
92
|
+
// process lifetime but a script only during load — so by driving the upgrade
|
|
93
|
+
// from this script (with the native binary not running) npm can overwrite the
|
|
94
|
+
// binary even on Windows, with no temp-file/rename/detached-helper dance.
|
|
95
|
+
// `--check` (JSON probe) and `--help` fall through to the native binary.
|
|
96
|
+
{
|
|
97
|
+
const updArgs = process.argv.slice(3);
|
|
98
|
+
const checkOnly = updArgs.includes("--check");
|
|
99
|
+
const wantsHelp = updArgs.some((a) => a === "--help" || a === "-h");
|
|
100
|
+
if (process.argv[2] === "update" && !checkOnly && !wantsHelp) {
|
|
101
|
+
runUpdate(binaryPath, updArgs); // never returns
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Exec the native binary, replacing this process
|
|
106
|
+
try {
|
|
107
|
+
execFileSync(binaryPath, process.argv.slice(2), {
|
|
108
|
+
stdio: "inherit",
|
|
109
|
+
env: process.env,
|
|
110
|
+
});
|
|
111
|
+
process.exit(0);
|
|
112
|
+
} catch (e) {
|
|
113
|
+
if (e.status !== null) {
|
|
114
|
+
process.exit(e.status);
|
|
115
|
+
}
|
|
116
|
+
process.exit(1);
|
|
91
117
|
}
|
|
92
|
-
process.exit(1);
|
|
93
118
|
}
|
|
94
119
|
|
|
95
120
|
// Performs `kcap update`: upgrade the global npm package, then refresh
|
|
96
121
|
// user-scope skills/hooks. Always exits the process; never returns.
|
|
97
|
-
function runUpdate(binaryPath) {
|
|
122
|
+
function runUpdate(binaryPath, updArgs) {
|
|
98
123
|
// On Windows, `npm` is a .cmd shim; Node refuses to spawn .cmd/.bat directly
|
|
99
124
|
// (2024 command-injection fix), so route npm through a shell there. Our npm
|
|
100
125
|
// args are static, so shell use is safe.
|
|
@@ -127,12 +152,13 @@ function runUpdate(binaryPath) {
|
|
|
127
152
|
// child fully EXITS before we run npm, so the binary file is unlocked when
|
|
128
153
|
// npm overwrites it (matters on Windows). `--no-update-check` keeps the
|
|
129
154
|
// background nudge from racing onto stderr.
|
|
155
|
+
let info = null;
|
|
130
156
|
try {
|
|
131
|
-
const out = execFileSync(binaryPath,
|
|
157
|
+
const out = execFileSync(binaryPath, probeArgs(updArgs), { encoding: "utf8" });
|
|
132
158
|
// The probe prints one JSON line; take the last {...} line in case anything
|
|
133
159
|
// else (e.g. a git-remote warning) landed on stdout first.
|
|
134
160
|
const line = out.split(/\r?\n/).reverse().find((l) => l.trim().startsWith("{"));
|
|
135
|
-
|
|
161
|
+
info = JSON.parse(line);
|
|
136
162
|
// Only short-circuit when the probe is CONFIDENT we're up to date: `newer`
|
|
137
163
|
// explicitly false AND a known current version. `newer: null` (version
|
|
138
164
|
// unknown / check failed) falls through to the upgrade rather than stranding
|
|
@@ -177,7 +203,7 @@ function runUpdate(binaryPath) {
|
|
|
177
203
|
process.exit(1);
|
|
178
204
|
}
|
|
179
205
|
|
|
180
|
-
const res = spawnSync("npm", ["install", "-g",
|
|
206
|
+
const res = spawnSync("npm", ["install", "-g", resolveInstallSpec(info)], {
|
|
181
207
|
stdio: "inherit",
|
|
182
208
|
windowsHide: true,
|
|
183
209
|
...npmOpts,
|
|
@@ -211,3 +237,5 @@ function runUpdate(binaryPath) {
|
|
|
211
237
|
|
|
212
238
|
process.exit(0);
|
|
213
239
|
}
|
|
240
|
+
|
|
241
|
+
module.exports = { resolveInstallSpec, probeArgs };
|
package/kcap/.mcp.json
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"command": "kcap",
|
|
15
15
|
"args": ["mcp", "flows"],
|
|
16
16
|
"cwd": "${CLAUDE_PROJECT_DIR}",
|
|
17
|
-
"description": "Structured AI
|
|
17
|
+
"description": "Structured AI agent flows — start_review_flow, submit_review_round, get_review_flow_status, close_review_flow (review-specific) plus their generic equivalents start_flow, send_to_participant, get_flow_status, close_flow (any flow-definition catalog id). Launches a SEPARATE hosted participant agent through your daemon and iterates to sign-off; requires `kcap login` and a running daemon with this repo checked out (the tools are inert otherwise). Use only when the user explicitly asks for a review flow / agent flow / to submit for review — for an ordinary 'review my PR' or 'code review' request, review directly and do not call these tools."
|
|
18
18
|
},
|
|
19
19
|
"kcap-memory": {
|
|
20
20
|
"command": "kcap",
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: agent-flows
|
|
3
|
+
description: >-
|
|
4
|
+
This skill should be used ONLY when the user explicitly asks to run a
|
|
5
|
+
structured agent *flow* by name or definition id — e.g. "start a flow",
|
|
6
|
+
"run the code-review flow", "run the X flow", "use flow definition X",
|
|
7
|
+
"kick off an agent flow", or wants an iterative loop run by a separate
|
|
8
|
+
hosted participant agent that continues until sign-off. It covers the same
|
|
9
|
+
underlying tools as the `review-flows` skill (`start_review_flow` etc. are
|
|
10
|
+
aliases of the generic tools documented here) — use `review-flows` for the
|
|
11
|
+
two built-in review kinds (`spec-review`, `code-review`) and this skill for
|
|
12
|
+
any other flow definition, or when the user names a `definition_id`
|
|
13
|
+
explicitly. Do NOT use this skill (and do NOT call the flows MCP tools) for
|
|
14
|
+
an ordinary request such as "review my PR", "do X for me", or "check this
|
|
15
|
+
over" where the user just wants you to do the work yourself — perform that
|
|
16
|
+
work directly instead.
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
# Agent Flows
|
|
20
|
+
|
|
21
|
+
Use the `kcap mcp flows` MCP tools (`start_flow`, `send_to_participant`, `get_flow_status`, `close_flow`) to run a structured agent **flow**: your work is handed to a **separate, hosted participant agent** driven by a flow definition from the server's catalog, which returns a result per that definition's markers (e.g. `FINDINGS:` / `NO FINDINGS` for the review-style built-ins); you address the result and keep iterating until the definition's clean/complete signal. This is a deliberate, heavier workflow — use it only when the user explicitly opts into it.
|
|
22
|
+
|
|
23
|
+
## When NOT to use this skill / these tools
|
|
24
|
+
|
|
25
|
+
These tools do **not** perform the work themselves — they hand it off to a separate hosted participant agent running a named flow definition. If the user simply asked *you* to do something in a normal session — e.g. "review my PR", "review this diff", "check this spec", "do X" — just do it yourself and report the result directly. Do **NOT** call `start_flow` / `send_to_participant` for an ordinary request; that would spin up a hosted agent the user did not ask for.
|
|
26
|
+
|
|
27
|
+
Only start a flow when the user explicitly asks for a flow — e.g. "start a flow", "run the code-review flow", "use flow definition X", or "re-review after I address the findings" via a flow.
|
|
28
|
+
|
|
29
|
+
## Choosing the flow definition
|
|
30
|
+
|
|
31
|
+
Once the user has explicitly opted into a flow (see above), pick the `definition_id`:
|
|
32
|
+
|
|
33
|
+
- Spec or design document → `definition_id: "spec-review"` (built-in; same as `review-flows`' `spec-review` kind)
|
|
34
|
+
- Code changes or a pull request → `definition_id: "code-review"` (built-in; same as `review-flows`' `code-review` kind)
|
|
35
|
+
- Anything else → the definition id the user named, or one you look up in the server's flow-definition catalog at `/admin/flows`. If you're unsure which definition applies, ask the user rather than guessing.
|
|
36
|
+
|
|
37
|
+
## If the flows MCP tools are not loaded
|
|
38
|
+
|
|
39
|
+
If `start_flow` / `send_to_participant` are not among the tools available in this session, do NOT try to obtain them:
|
|
40
|
+
|
|
41
|
+
- Do NOT run `kcap mcp flows` from a shell, do NOT handshake it over stdio/JSON-RPC, and do NOT edit any MCP configuration.
|
|
42
|
+
- The absence is deliberate: hosted flow participants run with all MCP servers stripped, so a participant cannot start a nested flow.
|
|
43
|
+
- If you were asked to do work and these tools are absent, you are most likely the hosted participant inside an existing flow. This skill does not apply to you — skip the workflow below entirely. Perform the requested work directly and end your final message with the definition's result markers (for the review-style built-ins, that's a final message starting with `FINDINGS:` followed by your findings, or `NO FINDINGS`; check any instructions you were given for a custom definition's actual markers). Your final message is captured automatically; no tool call is needed to deliver it.
|
|
44
|
+
|
|
45
|
+
## Core rules
|
|
46
|
+
|
|
47
|
+
1. **Start exactly one flow per user task.** Call `start_flow` once and hold the returned `flow_run_id`. Do NOT start a new flow for follow-up rounds — reuse the same ID.
|
|
48
|
+
2. **After receiving a non-clean result**, address it, then call `send_to_participant` with the same `flow_run_id` and the updated message.
|
|
49
|
+
3. **Do NOT finish the user task while the flow has unresolved results.** Keep iterating until the definition's clean/complete signal.
|
|
50
|
+
4. **Only call `close_flow` after the clean signal.** The run stays open until you explicitly close it — don't rely on it closing itself. Then report completion to the user.
|
|
51
|
+
5. **If participant output is unclear or requires user input**, pause and ask the user before proceeding.
|
|
52
|
+
6. **Never start a nested flow.** If you are the hosted participant (see above), do not call these tools yourself.
|
|
53
|
+
7. **Single participant.** In Phase D every flow definition has exactly one participant, `reviewer`. `send_to_participant` with any other `participant` value is rejected by the server, which names the valid participant in its error.
|
|
54
|
+
8. **For a code review flow (`definition_id: "code-review"`), do NOT ask the participant to run tests.** CI covers test execution; participant feedback is on correctness, design, and adherence to conventions.
|
|
55
|
+
|
|
56
|
+
## Guardrail errors
|
|
57
|
+
|
|
58
|
+
The server enforces per-run budgets; watch for these in tool error responses:
|
|
59
|
+
|
|
60
|
+
- **`400` containing `max_rounds (N) reached for this run — close the flow.`** — the run is still **open**, it's just hit its round cap. Stop submitting further rounds, summarize what you have, and call `close_flow`.
|
|
61
|
+
- **`400` containing `budget_exceeded: …`** — the run has **already failed** and the participant agent has stopped. Report this to the user; do NOT retry and do NOT call `close_flow` — closing a failed run overwrites the failure status in the read model (the projector flips `failed` → `closed`), hiding what went wrong.
|
|
62
|
+
- **A round that exceeds the definition's `round_timeout`** lands as a terminal **`unclear`** round, with the timeout explained in its result text — if you check round status programmatically, look for `unclear` and read the text for the timeout reason. The run itself stays open — you may submit another round or close the flow.
|
|
63
|
+
- **Idle runs are auto-reaped** after the definition's `idle_ttl` (server default 24h). Don't rely on this — always call `close_flow` yourself once you're done, whether the outcome was clean or you're abandoning the task.
|
|
64
|
+
|
|
65
|
+
## Workflow
|
|
66
|
+
|
|
67
|
+
```
|
|
68
|
+
start_flow(definition_id, target_kind, target_ref, target_title, context)
|
|
69
|
+
→ participant returns a result per the definition's markers (e.g. FINDINGS: … | NO FINDINGS)
|
|
70
|
+
|
|
71
|
+
if clean (e.g. NO FINDINGS):
|
|
72
|
+
close_flow(flow_run_id)
|
|
73
|
+
report completion to user
|
|
74
|
+
DONE
|
|
75
|
+
|
|
76
|
+
if not clean (e.g. FINDINGS:):
|
|
77
|
+
address the result
|
|
78
|
+
send_to_participant(flow_run_id, participant="reviewer", message=…)
|
|
79
|
+
→ repeat until clean
|
|
80
|
+
close_flow(flow_run_id)
|
|
81
|
+
report completion to user
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Tool reference
|
|
85
|
+
|
|
86
|
+
| Tool | Required args | Optional args | When to call |
|
|
87
|
+
|---|---|---|---|
|
|
88
|
+
| `start_flow` | `definition_id` (e.g. `spec-review`, `code-review`, or a custom catalog id), `target_kind` (what is being worked on: `spec`, `code`, `pr`, `branch`, `file`, etc.), `target_ref` (a path, branch name, or PR URL/number that identifies the target), `target_title` (short human-readable title), `context` (background context: what to focus on, constraints, definition of done) | `instructions`, `mode` (`context-only` — optional; by default, on the same machine, the participant's worktree is mirrored from your working tree including uncommitted changes, so it reads the actual source. Pass `context-only` to opt out and treat the submitted context as authoritative) | Once, at the start of a flow task. |
|
|
89
|
+
| `send_to_participant` | `flow_run_id`, `participant` (Phase D flows have a single participant: `reviewer`), `message` | `instructions`, `async` (defaults to `true`) | After addressing a non-clean result. Pass the same `flow_run_id` and the updated message. |
|
|
90
|
+
| `get_flow_status` | `flow_run_id` | — | Poll or check the current status of a flow run (running, waiting, completed, failed). |
|
|
91
|
+
| `close_flow` | `flow_run_id` | — | Only after the definition's clean signal — or when abandoning the task early; the run otherwise stays open until closed. |
|
|
92
|
+
|
|
93
|
+
## Example (custom definition)
|
|
94
|
+
|
|
95
|
+
```
|
|
96
|
+
# Step 1 — start (all five required args must be provided; on the same machine the participant sees
|
|
97
|
+
# your working tree, uncommitted changes included — pass mode="context-only" to opt out)
|
|
98
|
+
start_flow(
|
|
99
|
+
definition_id="code-review",
|
|
100
|
+
target_kind="branch",
|
|
101
|
+
target_ref="feature/add-null-check",
|
|
102
|
+
target_title="Add null check on user input",
|
|
103
|
+
context="Review the diff on this branch for correctness and adherence to project conventions."
|
|
104
|
+
)
|
|
105
|
+
# → returns flow_run_id, e.g. "flow_abc123"
|
|
106
|
+
# → participant returns FINDINGS: missing null check on line 42
|
|
107
|
+
|
|
108
|
+
# Step 2 — address findings, then send a follow-up to the reviewer participant
|
|
109
|
+
send_to_participant(
|
|
110
|
+
flow_run_id="flow_abc123",
|
|
111
|
+
participant="reviewer",
|
|
112
|
+
message="Fixed null check on line 42. Updated diff attached."
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
# Step 3 — participant returns NO FINDINGS
|
|
116
|
+
close_flow(flow_run_id="flow_abc123")
|
|
117
|
+
# Report to user: flow complete, all findings resolved
|
|
118
|
+
```
|
|
@@ -15,6 +15,8 @@ description: >-
|
|
|
15
15
|
|
|
16
16
|
Use the `kcap mcp flows` MCP tools (`start_review_flow`, `submit_review_round`, …) to run a structured review **flow**: your work is submitted to a **separate, hosted reviewer** agent, which returns findings; you address them and keep iterating until the reviewer returns `NO FINDINGS`. This is a deliberate, heavier workflow — use it only when the user explicitly opts into it.
|
|
17
17
|
|
|
18
|
+
These four tools are aliases of the generic flow tools (`start_flow`, `send_to_participant`, `get_flow_status`, `close_flow`) — see the `agent-flows` skill for non-review flows.
|
|
19
|
+
|
|
18
20
|
## When NOT to use this skill / these tools
|
|
19
21
|
|
|
20
22
|
These tools do **not** perform a review — they hand the work off to a separate hosted reviewer. If the user simply asked *you* to review something in a normal session — e.g. "review my PR", "review this diff", "code review this", "look over this spec" — just perform the review yourself and report your findings directly. Do **NOT** call `start_review_flow` / `submit_review_round` for an ordinary review request; that would spin up a hosted reviewer the user did not ask for.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kurrent/kcap",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.2",
|
|
4
4
|
"description": "CLI companion for Kurrent Capacitor — records and visualizes Claude Code sessions",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE.md",
|
|
6
6
|
"repository": {
|
|
@@ -14,12 +14,12 @@
|
|
|
14
14
|
"postinstall": "node bin/postinstall.js"
|
|
15
15
|
},
|
|
16
16
|
"optionalDependencies": {
|
|
17
|
-
"@kurrent/kcap-darwin-arm64": "0.9.
|
|
18
|
-
"@kurrent/kcap-linux-x64": "0.9.
|
|
19
|
-
"@kurrent/kcap-linux-arm64": "0.9.
|
|
20
|
-
"@kurrent/kcap-linux-musl-x64": "0.9.
|
|
21
|
-
"@kurrent/kcap-linux-musl-arm64": "0.9.
|
|
22
|
-
"@kurrent/kcap-win-x64": "0.9.
|
|
17
|
+
"@kurrent/kcap-darwin-arm64": "0.9.2",
|
|
18
|
+
"@kurrent/kcap-linux-x64": "0.9.2",
|
|
19
|
+
"@kurrent/kcap-linux-arm64": "0.9.2",
|
|
20
|
+
"@kurrent/kcap-linux-musl-x64": "0.9.2",
|
|
21
|
+
"@kurrent/kcap-linux-musl-arm64": "0.9.2",
|
|
22
|
+
"@kurrent/kcap-win-x64": "0.9.2"
|
|
23
23
|
},
|
|
24
24
|
"files": [
|
|
25
25
|
"bin/",
|