@blastin-dev/clocktopus-cli 0.2.0 → 0.2.1
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 +89 -34
- package/dist/src/commands/agent/disable.d.ts +8 -1
- package/dist/src/commands/agent/disable.d.ts.map +1 -1
- package/dist/src/commands/agent/disable.js +79 -45
- package/dist/src/commands/agent/doctor.d.ts.map +1 -1
- package/dist/src/commands/agent/doctor.js +146 -75
- package/dist/src/commands/agent/hook.d.ts +4 -1
- package/dist/src/commands/agent/hook.d.ts.map +1 -1
- package/dist/src/commands/agent/hook.js +152 -15
- package/dist/src/commands/agent/setup.d.ts +17 -10
- package/dist/src/commands/agent/setup.d.ts.map +1 -1
- package/dist/src/commands/agent/setup.js +208 -69
- package/dist/src/commands/agent/status.d.ts.map +1 -1
- package/dist/src/commands/agent/status.js +46 -24
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +18 -4
- package/dist/src/lib/agent-config.d.ts +18 -3
- package/dist/src/lib/agent-config.d.ts.map +1 -1
- package/dist/src/lib/agent-config.js +44 -19
- package/dist/src/lib/agent-hook-state.d.ts +9 -1
- package/dist/src/lib/agent-hook-state.d.ts.map +1 -1
- package/dist/src/lib/agent-hook-state.js +21 -2
- package/dist/src/lib/agents.d.ts +115 -0
- package/dist/src/lib/agents.d.ts.map +1 -0
- package/dist/src/lib/agents.js +245 -0
- package/dist/src/lib/codex-config.d.ts +166 -0
- package/dist/src/lib/codex-config.d.ts.map +1 -0
- package/dist/src/lib/codex-config.js +441 -0
- package/dist/src/lib/codex-config.test.d.ts +2 -0
- package/dist/src/lib/codex-config.test.d.ts.map +1 -0
- package/dist/src/lib/codex-config.test.js +359 -0
- package/dist/src/lib/opencode-config.d.ts +108 -0
- package/dist/src/lib/opencode-config.d.ts.map +1 -0
- package/dist/src/lib/opencode-config.js +330 -0
- package/dist/src/lib/opencode-config.test.d.ts +2 -0
- package/dist/src/lib/opencode-config.test.d.ts.map +1 -0
- package/dist/src/lib/opencode-config.test.js +140 -0
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -67,34 +67,77 @@ Show clock signals for a given date.
|
|
|
67
67
|
### `clocktopus agent setup`
|
|
68
68
|
|
|
69
69
|
```
|
|
70
|
-
clocktopus agent setup [--name <label>] [--force]
|
|
70
|
+
clocktopus agent setup [--name <label>] [--force] [--agent <ids>]
|
|
71
71
|
```
|
|
72
72
|
|
|
73
73
|
Configures this machine to report AI agent spend — token cost, active time,
|
|
74
74
|
and the repository each session worked in — so the true cost of a piece of
|
|
75
75
|
work can be measured as human time × rate **plus** agent spend.
|
|
76
76
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
77
|
+
One command, however many agents you have. It looks for each supported CLI
|
|
78
|
+
on PATH and configures what it finds:
|
|
79
|
+
|
|
80
|
+
```
|
|
81
|
+
Agents on this machine
|
|
82
|
+
|
|
83
|
+
1 Claude Code 2.1.234 already configured
|
|
84
|
+
2 Codex CLI 0.147.0 not configured
|
|
85
|
+
|
|
86
|
+
Which should report to Clocktopus? [all]:
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
With one agent installed there is no question to ask, so it just runs. With
|
|
90
|
+
several it prompts; `--agent` skips the prompt, and a non-interactive shell
|
|
91
|
+
configures everything found and says so.
|
|
92
|
+
|
|
93
|
+
| Agent | `--agent` id | Writes |
|
|
94
|
+
| ----------- | ------------ | --------------------------------------------- |
|
|
95
|
+
| Claude Code | `claude` | `~/.claude/settings.json` |
|
|
96
|
+
| Codex CLI | `codex` | `~/.codex/config.toml`, `~/.codex/hooks.json` |
|
|
97
|
+
| OpenCode | `opencode` | `~/.config/opencode/plugin/clocktopus.js` |
|
|
98
|
+
|
|
99
|
+
It mints **one** ingest token for the machine, writes each agent's exporter
|
|
100
|
+
configuration and `SessionStart` / `SessionEnd` hooks, then confirms the
|
|
101
|
+
receiver accepts the token before reporting success.
|
|
80
102
|
|
|
81
103
|
| Option | Description |
|
|
82
104
|
| ---------------- | --------------------------------------------------------------------------------------------------------------- |
|
|
83
105
|
| `--name <label>` | Label for this machine's token. Defaults to the hostname — keep it recognisable, since revocation is per token. |
|
|
84
106
|
| `--force` | Mint a replacement token instead of reusing the one already installed. |
|
|
107
|
+
| `-a, --agent` | Configure only these agents. Repeatable, or comma-separated: `--agent claude,codex`. |
|
|
85
108
|
|
|
86
109
|
Re-running without `--force` is safe and idempotent: it repairs the hooks and
|
|
87
|
-
refreshes the endpoint while keeping the existing token.
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
110
|
+
refreshes the endpoint while keeping the existing token. Installing a second
|
|
111
|
+
agent later and re-running costs nothing — the new one inherits the token the
|
|
112
|
+
first is already using rather than minting a second against the same machine.
|
|
113
|
+
|
|
114
|
+
**Restart the agent afterwards.** Every exporter and hook reads its
|
|
115
|
+
configuration once, at process start, so a session that is already open will
|
|
116
|
+
not pick any of it up. This is the most common reason a correct setup appears
|
|
117
|
+
to do nothing.
|
|
118
|
+
|
|
119
|
+
**OpenCode is a plugin, not a hook.** It has no hook system, so setup
|
|
120
|
+
writes one generated file into `~/.config/opencode/plugin/`, which OpenCode
|
|
121
|
+
auto-loads. Your `opencode.json` is not touched — that file is your model,
|
|
122
|
+
provider and permission configuration, and leaving it alone removes a whole
|
|
123
|
+
class of ways to break your setup. Uninstalling is deleting the file.
|
|
124
|
+
|
|
125
|
+
OpenCode also has `experimental.openTelemetry`, which looks like an easier
|
|
126
|
+
route and is not one: its spans carry your prompts and the model's replies
|
|
127
|
+
verbatim, with no way to switch that off, and no cost or repository. The
|
|
128
|
+
plugin reads OpenCode's own per-message totals instead and sends counts and
|
|
129
|
+
identifiers only.
|
|
130
|
+
|
|
131
|
+
**Codex needs one extra step, once.** It will not run a `hooks.json` it has
|
|
132
|
+
not been shown, so the next Codex session asks — answer _Trust all and
|
|
133
|
+
continue_. Until you do, Codex reports what your sessions cost but not which
|
|
134
|
+
repository they were spent on. `clocktopus agent doctor` reports the state
|
|
135
|
+
until it is approved.
|
|
136
|
+
|
|
137
|
+
Everything for an agent lands in that agent's own config rather than a shell
|
|
138
|
+
profile. That single source of truth per agent is what lets `agent doctor`
|
|
139
|
+
say which value is actually in force. Note the ingest token is stored there
|
|
140
|
+
in plaintext: if you keep `~/.claude` or `~/.codex` in a dotfiles repository,
|
|
98
141
|
ignore the file.
|
|
99
142
|
|
|
100
143
|
Setup then checks the repository you ran it in, because configuring the
|
|
@@ -124,21 +167,27 @@ claim; this is the evidence.
|
|
|
124
167
|
|
|
125
168
|
The two ingest lanes are reported separately because they fail separately:
|
|
126
169
|
|
|
127
|
-
| Lane
|
|
128
|
-
|
|
|
129
|
-
| Hook
|
|
130
|
-
|
|
|
170
|
+
| Lane | Carries | If it alone goes quiet |
|
|
171
|
+
| ----- | -------------------------------- | -------------------------------------------------------- |
|
|
172
|
+
| Hook | repository, branch, commit range | spend is recorded but cannot be attributed to a project |
|
|
173
|
+
| Spend | tokens, cost, active time | sessions show `$0.00`, indistinguishable from cheap work |
|
|
174
|
+
|
|
175
|
+
It also lists every supported agent found on this machine, including any
|
|
176
|
+
that are installed but _not_ reporting — a state the server-side lanes
|
|
177
|
+
cannot show, because those sessions simply never arrive.
|
|
131
178
|
|
|
132
179
|
That second failure is why there is no single "connected" line — it is the
|
|
133
180
|
one that silently understates what work cost.
|
|
134
181
|
|
|
135
182
|
### `clocktopus agent doctor`
|
|
136
183
|
|
|
137
|
-
Checks every link in the chain and names the first one that is
|
|
138
|
-
|
|
139
|
-
hook command actually executes,
|
|
140
|
-
|
|
141
|
-
|
|
184
|
+
Checks every link in the chain, per agent, and names the first one that is
|
|
185
|
+
broken: the config file parses, the exporter is configured, both hooks are
|
|
186
|
+
installed, the hook command actually executes, Codex has approved its hooks,
|
|
187
|
+
the receiver accepts the token, no other file is shadowing the
|
|
188
|
+
configuration, and whether that agent needs restarting. The repository
|
|
189
|
+
checks — attached to a project, delivering commits — run once at the end,
|
|
190
|
+
since they are facts about the repo rather than about any one agent.
|
|
142
191
|
|
|
143
192
|
The receiver check is the one that cannot be done locally — it crosses the
|
|
144
193
|
network to a different host than the dashboard, so a wrong endpoint and a
|
|
@@ -147,15 +196,21 @@ revoked token are otherwise indistinguishable from an idle afternoon.
|
|
|
147
196
|
### `clocktopus agent disable`
|
|
148
197
|
|
|
149
198
|
```
|
|
150
|
-
clocktopus agent disable [--revoke]
|
|
199
|
+
clocktopus agent disable [--revoke] [--agent <ids>]
|
|
151
200
|
```
|
|
152
201
|
|
|
153
|
-
Removes this machine's telemetry configuration, leaving any
|
|
154
|
-
|
|
202
|
+
Removes this machine's telemetry configuration from every agent, leaving any
|
|
203
|
+
hooks and settings you added yourself untouched.
|
|
204
|
+
|
|
205
|
+
| Option | Description |
|
|
206
|
+
| ------------- | --------------------------------------------------------------------------- |
|
|
207
|
+
| `--revoke` | Also revoke the ingest token, making it useless everywhere. |
|
|
208
|
+
| `-a, --agent` | Remove only these agents (`claude`, `codex`), leaving the others reporting. |
|
|
155
209
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
210
|
+
`--revoke` is ignored when combined with `--agent`: the token is shared by
|
|
211
|
+
every agent on the machine, so killing it while another one still has it
|
|
212
|
+
written would leave that agent posting into a 401 forever — which looks
|
|
213
|
+
like an idle week rather than a broken install.
|
|
159
214
|
|
|
160
215
|
Stopping and revoking are separate on purpose: removing the local
|
|
161
216
|
configuration stops _this_ machine reporting, but does nothing about a token
|
|
@@ -173,7 +228,7 @@ ig.json`
|
|
|
173
228
|
Run `clocktopus logout` to clear stored credentials.
|
|
174
229
|
|
|
175
230
|
The agent telemetry setup deliberately keeps **no** copy of the ingest token
|
|
176
|
-
here. It has to live in
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
231
|
+
here. It has to live in each agent's own config for that agent's exporter to
|
|
232
|
+
read it, and a second copy would be another thing to leak and another thing
|
|
233
|
+
to fall out of date. Only the token id (needed to revoke) and its display
|
|
234
|
+
prefix are kept.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Removes this machine's telemetry configuration.
|
|
2
|
+
* Removes this machine's telemetry configuration, from every agent.
|
|
3
3
|
*
|
|
4
4
|
* Stopping and revoking are separate on purpose. Removing the local
|
|
5
5
|
* configuration stops *this* machine reporting; it does nothing about a
|
|
@@ -7,8 +7,15 @@
|
|
|
7
7
|
* repository. `--revoke` is the one that makes the token useless everywhere,
|
|
8
8
|
* and it is opt-in because the common case — moving machines, reinstalling —
|
|
9
9
|
* should not invalidate a token that CI or another workstation still uses.
|
|
10
|
+
*
|
|
11
|
+
* `--agent` narrows it to one, which is the only way to stop reporting from
|
|
12
|
+
* Codex while leaving Claude Code alone. Without it every agent is cleaned,
|
|
13
|
+
* including ones no longer installed — an uninstalled agent's config file
|
|
14
|
+
* outlives it, and a token left in a file nobody looks at is the worst
|
|
15
|
+
* place for one to sit.
|
|
10
16
|
*/
|
|
11
17
|
export declare function disableCommand(options: {
|
|
12
18
|
revoke?: boolean;
|
|
19
|
+
agent?: string[];
|
|
13
20
|
}): Promise<void>;
|
|
14
21
|
//# sourceMappingURL=disable.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"disable.d.ts","sourceRoot":"","sources":["../../../../src/commands/agent/disable.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"disable.d.ts","sourceRoot":"","sources":["../../../../src/commands/agent/disable.ts"],"names":[],"mappings":"AASA;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,cAAc,CAAC,OAAO,EAAE;IAC5C,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;CAClB,GAAG,OAAO,CAAC,IAAI,CAAC,CAyFhB"}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { clearHookState } from "../../lib/agent-hook-state.js";
|
|
2
|
+
import { isConfigParseError, surveyAgents } from "../../lib/agents.js";
|
|
2
3
|
import { ApiError, request } from "../../lib/api.js";
|
|
3
|
-
import { readSettings, removeTelemetrySettings, SettingsParseError, writeSettings, } from "../../lib/claude-settings.js";
|
|
4
4
|
import { clearAgentConfig, getAgentConfig, isLoggedIn, } from "../../lib/config.js";
|
|
5
5
|
/**
|
|
6
|
-
* Removes this machine's telemetry configuration.
|
|
6
|
+
* Removes this machine's telemetry configuration, from every agent.
|
|
7
7
|
*
|
|
8
8
|
* Stopping and revoking are separate on purpose. Removing the local
|
|
9
9
|
* configuration stops *this* machine reporting; it does nothing about a
|
|
@@ -11,62 +11,96 @@ import { clearAgentConfig, getAgentConfig, isLoggedIn, } from "../../lib/config.
|
|
|
11
11
|
* repository. `--revoke` is the one that makes the token useless everywhere,
|
|
12
12
|
* and it is opt-in because the common case — moving machines, reinstalling —
|
|
13
13
|
* should not invalidate a token that CI or another workstation still uses.
|
|
14
|
+
*
|
|
15
|
+
* `--agent` narrows it to one, which is the only way to stop reporting from
|
|
16
|
+
* Codex while leaving Claude Code alone. Without it every agent is cleaned,
|
|
17
|
+
* including ones no longer installed — an uninstalled agent's config file
|
|
18
|
+
* outlives it, and a token left in a file nobody looks at is the worst
|
|
19
|
+
* place for one to sit.
|
|
14
20
|
*/
|
|
15
21
|
export async function disableCommand(options) {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
const
|
|
20
|
-
if (
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
if (removedEnvKeys.length > 0 || removedHooks) {
|
|
25
|
-
writeSettings(result.settings, current.path);
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
catch (error) {
|
|
30
|
-
if (error instanceof SettingsParseError) {
|
|
31
|
-
console.error(`✗ ${error.message}`);
|
|
22
|
+
const survey = surveyAgents();
|
|
23
|
+
const requested = (options.agent ?? []).flatMap((value) => value.split(",").map((part) => part.trim().toLowerCase()));
|
|
24
|
+
if (requested.length > 0) {
|
|
25
|
+
const unknown = requested.filter((id) => !survey.some((entry) => entry.agent.id === id));
|
|
26
|
+
if (unknown.length > 0) {
|
|
27
|
+
console.error(`Unknown agent: ${unknown.join(", ")}. Valid ids: ${survey
|
|
28
|
+
.map((entry) => entry.agent.id)
|
|
29
|
+
.join(", ")}.`);
|
|
32
30
|
process.exit(1);
|
|
33
31
|
}
|
|
34
|
-
throw error;
|
|
35
32
|
}
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
" login' and try again — the local configuration was still removed.");
|
|
33
|
+
const targets = requested.length > 0
|
|
34
|
+
? survey.filter((entry) => requested.includes(entry.agent.id))
|
|
35
|
+
: survey;
|
|
36
|
+
const cleaned = [];
|
|
37
|
+
for (const entry of targets) {
|
|
38
|
+
try {
|
|
39
|
+
const { removed } = entry.agent.remove();
|
|
40
|
+
if (removed.length > 0)
|
|
41
|
+
cleaned.push({ label: entry.agent.label, removed });
|
|
46
42
|
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
}
|
|
52
|
-
catch (error) {
|
|
53
|
-
console.error(`✗ Could not revoke the token: ${error instanceof ApiError || error instanceof Error
|
|
54
|
-
? error.message
|
|
55
|
-
: "unknown error"}`);
|
|
56
|
-
console.error(" The local configuration was still removed.");
|
|
43
|
+
catch (error) {
|
|
44
|
+
if (isConfigParseError(error)) {
|
|
45
|
+
console.error(`✗ ${error.message}`);
|
|
46
|
+
process.exit(1);
|
|
57
47
|
}
|
|
48
|
+
// A failure on one agent must not leave the others reporting.
|
|
49
|
+
console.error(`✗ ${entry.agent.label}: ${error instanceof Error ? error.message : "unknown error"}`);
|
|
58
50
|
}
|
|
59
51
|
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
52
|
+
const agent = getAgentConfig();
|
|
53
|
+
// Revoking while another agent on this machine still has the token
|
|
54
|
+
// written would leave that one posting into a 401 forever, which looks
|
|
55
|
+
// like an idle week rather than a broken install.
|
|
56
|
+
const partial = requested.length > 0 && requested.length < survey.length;
|
|
57
|
+
if (options.revoke && partial) {
|
|
58
|
+
console.log("⚠ --revoke was ignored: it kills the token everywhere, and this run only\n" +
|
|
59
|
+
" cleaned some of the agents on this machine. Re-run without --agent to\n" +
|
|
60
|
+
" remove all of them, then revoke.");
|
|
61
|
+
}
|
|
62
|
+
else if (options.revoke) {
|
|
63
|
+
await revokeToken(agent);
|
|
64
|
+
}
|
|
65
|
+
if (!partial) {
|
|
66
|
+
clearAgentConfig();
|
|
67
|
+
clearHookState();
|
|
68
|
+
}
|
|
69
|
+
if (cleaned.length === 0) {
|
|
63
70
|
console.log("Nothing to remove — agent telemetry was not configured here.");
|
|
64
71
|
return;
|
|
65
72
|
}
|
|
66
|
-
|
|
67
|
-
|
|
73
|
+
for (const { label, removed } of cleaned) {
|
|
74
|
+
console.log(`✓ ${label}: removed ${removed.join(", ")}.`);
|
|
75
|
+
}
|
|
76
|
+
if (!options.revoke || partial) {
|
|
68
77
|
console.log("\nThe ingest token is still valid. Revoke it with\n" +
|
|
69
78
|
" clocktopus agent disable --revoke");
|
|
70
79
|
}
|
|
71
|
-
console.log(
|
|
80
|
+
console.log(`\nRestart ${cleaned
|
|
81
|
+
.map((c) => c.label)
|
|
82
|
+
.join(" and ")} to stop the current session reporting.`);
|
|
83
|
+
}
|
|
84
|
+
async function revokeToken(agent) {
|
|
85
|
+
if (!agent.tokenId) {
|
|
86
|
+
console.log("⚠ No token id on record, so nothing was revoked. This machine's token\n" +
|
|
87
|
+
" was minted by an older version or on another machine — revoke it\n" +
|
|
88
|
+
" from the dashboard instead.");
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
if (!isLoggedIn()) {
|
|
92
|
+
console.log("⚠ Not logged in, so the token could not be revoked. Run 'clocktopus\n" +
|
|
93
|
+
" login' and try again — the local configuration was still removed.");
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
try {
|
|
97
|
+
await request(`/api/agent/ingest-token?tokenId=${encodeURIComponent(agent.tokenId)}`, { method: "DELETE" });
|
|
98
|
+
console.log(`✓ Revoked ${agent.tokenPrefix ?? "the ingest token"}.`);
|
|
99
|
+
}
|
|
100
|
+
catch (error) {
|
|
101
|
+
console.error(`✗ Could not revoke the token: ${error instanceof ApiError || error instanceof Error
|
|
102
|
+
? error.message
|
|
103
|
+
: "unknown error"}`);
|
|
104
|
+
console.error(" The local configuration was still removed.");
|
|
105
|
+
}
|
|
72
106
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../../../src/commands/agent/doctor.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../../../src/commands/agent/doctor.ts"],"names":[],"mappings":"AAuCA,wBAAsB,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC,CA8CnD"}
|