@kairyou/agent-tools 0.1.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/LICENSE +21 -0
- package/README.md +156 -0
- package/README.zh-CN.md +149 -0
- package/config.default.jsonc +23 -0
- package/hooks/claude/.gitkeep +1 -0
- package/hooks/codex/.gitkeep +1 -0
- package/hooks/codex/usage-hook.mjs +157 -0
- package/hooks/common/.gitkeep +1 -0
- package/hooks/opencode/.gitkeep +1 -0
- package/lib/usage.mjs +1200 -0
- package/package.json +31 -0
- package/plugins/opencode/usage-plugin.mjs +95 -0
- package/plugins/opencode/usage-tui.mjs +49 -0
- package/scripts/install.mjs +551 -0
- package/skills/workflow/at-commit/SKILL.md +83 -0
- package/skills/workflow/at-review/SKILL.md +89 -0
- package/skills/workflow/at-simplify/SKILL.md +67 -0
- package/statusline/.gitkeep +1 -0
- package/statusline/claude/statusline.mjs +399 -0
- package/statusline/codex/.gitkeep +1 -0
package/package.json
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@kairyou/agent-tools",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Skills and runtime integrations for Codex, Claude Code, and opencode.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"engines": {
|
|
7
|
+
"node": ">=22"
|
|
8
|
+
},
|
|
9
|
+
"publishConfig": {
|
|
10
|
+
"access": "public",
|
|
11
|
+
"registry": "https://registry.npmjs.org/"
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
"config.default.jsonc",
|
|
15
|
+
"hooks/",
|
|
16
|
+
"lib/",
|
|
17
|
+
"plugins/",
|
|
18
|
+
"scripts/",
|
|
19
|
+
"skills/",
|
|
20
|
+
"statusline/"
|
|
21
|
+
],
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"jsonc-parser": "3.3.1"
|
|
24
|
+
},
|
|
25
|
+
"scripts": {
|
|
26
|
+
"test": "node --test tests/*.test.mjs"
|
|
27
|
+
},
|
|
28
|
+
"bin": {
|
|
29
|
+
"agent-tools": "./scripts/install.mjs"
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { queryProviderUsage } from "../../lib/usage.mjs";
|
|
2
|
+
|
|
3
|
+
const DEFAULT_REFRESH_MS = 60_000;
|
|
4
|
+
|
|
5
|
+
function toastMessage(text) {
|
|
6
|
+
return text.replace(/^API \| /, "");
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function firstString(value, keys) {
|
|
10
|
+
for (const key of keys) {
|
|
11
|
+
if (typeof value?.[key] === "string" && value[key].trim()) return value[key].trim();
|
|
12
|
+
}
|
|
13
|
+
return "";
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function providerContext(input) {
|
|
17
|
+
const info = input?.provider?.info || {};
|
|
18
|
+
const options = input?.provider?.options || {};
|
|
19
|
+
const providerName = String(input?.model?.providerID || info.id || "opencode");
|
|
20
|
+
const label = String(info.name || providerName);
|
|
21
|
+
return {
|
|
22
|
+
providerName,
|
|
23
|
+
provider: { name: label },
|
|
24
|
+
label,
|
|
25
|
+
baseUrl:
|
|
26
|
+
process.env.PROVIDER_USAGE_BASE_URL ||
|
|
27
|
+
firstString(options, ["baseURL", "baseUrl", "base_url"]),
|
|
28
|
+
key:
|
|
29
|
+
process.env.PROVIDER_USAGE_API_KEY ||
|
|
30
|
+
process.env.SUB2API_API_KEY ||
|
|
31
|
+
firstString(options, ["apiKey", "api_key"]),
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function refreshInterval(options) {
|
|
36
|
+
const configured = Number(options?.refreshMs || process.env.PROVIDER_USAGE_REFRESH_MS);
|
|
37
|
+
return Number.isFinite(configured) && configured >= 0 ? configured : DEFAULT_REFRESH_MS;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export const AgentToolsUsage = async ({ client }, options = {}) => {
|
|
41
|
+
const contexts = new Map();
|
|
42
|
+
const refreshedAt = new Map();
|
|
43
|
+
const inFlight = new Map();
|
|
44
|
+
const refreshMs = refreshInterval(options);
|
|
45
|
+
|
|
46
|
+
async function showUsage(text) {
|
|
47
|
+
if (!text) return;
|
|
48
|
+
try {
|
|
49
|
+
await client.tui.showToast({
|
|
50
|
+
body: {
|
|
51
|
+
title: "Provider usage",
|
|
52
|
+
message: toastMessage(text),
|
|
53
|
+
variant: "info",
|
|
54
|
+
duration: 8000,
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
} catch {
|
|
58
|
+
// Headless OpenCode sessions do not have a TUI to notify.
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async function refreshSession(sessionID) {
|
|
63
|
+
const context = contexts.get(sessionID);
|
|
64
|
+
if (!context?.baseUrl || !context?.key) return;
|
|
65
|
+
|
|
66
|
+
const cacheKey = context.baseUrl.replace(/\/+$/, "");
|
|
67
|
+
const now = Date.now();
|
|
68
|
+
if (now - (refreshedAt.get(cacheKey) || 0) < refreshMs) return;
|
|
69
|
+
if (inFlight.has(cacheKey)) return await inFlight.get(cacheKey);
|
|
70
|
+
|
|
71
|
+
refreshedAt.set(cacheKey, now);
|
|
72
|
+
const task = queryProviderUsage(context, {
|
|
73
|
+
agent: "opencode",
|
|
74
|
+
rememberSnapshot: true,
|
|
75
|
+
})
|
|
76
|
+
.then((result) => showUsage(result?.text || ""))
|
|
77
|
+
.catch(() => {})
|
|
78
|
+
.finally(() => inFlight.delete(cacheKey));
|
|
79
|
+
inFlight.set(cacheKey, task);
|
|
80
|
+
return await task;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return {
|
|
84
|
+
"chat.params": async (input) => {
|
|
85
|
+
contexts.set(input.sessionID, providerContext(input));
|
|
86
|
+
},
|
|
87
|
+
event: async ({ event }) => {
|
|
88
|
+
if (event.type === "session.idle") {
|
|
89
|
+
void refreshSession(event.properties.sessionID);
|
|
90
|
+
} else if (event.type === "session.deleted") {
|
|
91
|
+
contexts.delete(event.properties.sessionID);
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
};
|
|
95
|
+
};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
|
|
5
|
+
const AGENT_TOOLS_HOME = process.env.AGENT_TOOLS_HOME || join(homedir(), ".agent-tools");
|
|
6
|
+
const SNAPSHOT_PATH = join(AGENT_TOOLS_HOME, "cache", "usage-snapshot.json");
|
|
7
|
+
|
|
8
|
+
function toastMessage(text) {
|
|
9
|
+
return text.replace(/^API \| /, "");
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function latestUsage() {
|
|
13
|
+
try {
|
|
14
|
+
const data = JSON.parse(readFileSync(SNAPSHOT_PATH, "utf8"));
|
|
15
|
+
return Object.values(data?.items || {})
|
|
16
|
+
.filter((item) => typeof item?.text === "string" && item.text)
|
|
17
|
+
.sort((a, b) => String(b.updatedAt || "").localeCompare(String(a.updatedAt || "")))[0]?.text || "";
|
|
18
|
+
} catch {
|
|
19
|
+
return "";
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const tui = async (api) => {
|
|
24
|
+
api.keymap.registerLayer({
|
|
25
|
+
commands: [
|
|
26
|
+
{
|
|
27
|
+
name: "agent-tools.usage",
|
|
28
|
+
title: "Provider usage",
|
|
29
|
+
category: "Agent Tools",
|
|
30
|
+
namespace: "palette",
|
|
31
|
+
slashName: "at-usage",
|
|
32
|
+
run() {
|
|
33
|
+
const message = latestUsage();
|
|
34
|
+
api.ui.toast({
|
|
35
|
+
title: "Provider usage",
|
|
36
|
+
message: message ? toastMessage(message) : "Provider usage is not available yet",
|
|
37
|
+
variant: message ? "info" : "warning",
|
|
38
|
+
duration: 8000,
|
|
39
|
+
});
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
],
|
|
43
|
+
});
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export default {
|
|
47
|
+
id: "agent-tools-usage",
|
|
48
|
+
tui,
|
|
49
|
+
};
|