@blastin-dev/clocktopus-cli 0.1.3 → 0.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 +124 -6
- package/dist/src/commands/agent/disable.d.ts +14 -0
- package/dist/src/commands/agent/disable.d.ts.map +1 -0
- package/dist/src/commands/agent/disable.js +72 -0
- package/dist/src/commands/agent/doctor.d.ts +2 -0
- package/dist/src/commands/agent/doctor.d.ts.map +1 -0
- package/dist/src/commands/agent/doctor.js +235 -0
- package/dist/src/commands/agent/hook.d.ts +2 -0
- package/dist/src/commands/agent/hook.d.ts.map +1 -0
- package/dist/src/commands/agent/hook.js +231 -0
- package/dist/src/commands/agent/setup.d.ts +21 -0
- package/dist/src/commands/agent/setup.d.ts.map +1 -0
- package/dist/src/commands/agent/setup.js +194 -0
- package/dist/src/commands/agent/status.d.ts +2 -0
- package/dist/src/commands/agent/status.d.ts.map +1 -0
- package/dist/src/commands/agent/status.js +160 -0
- package/dist/src/commands/clock.d.ts +26 -4
- package/dist/src/commands/clock.d.ts.map +1 -1
- package/dist/src/commands/clock.js +99 -6
- package/dist/src/commands/login.d.ts.map +1 -1
- package/dist/src/commands/login.js +5 -5
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +45 -6
- package/dist/src/lib/agent-config.d.ts +41 -0
- package/dist/src/lib/agent-config.d.ts.map +1 -0
- package/dist/src/lib/agent-config.js +143 -0
- package/dist/src/lib/agent-hook-state.d.ts +36 -0
- package/dist/src/lib/agent-hook-state.d.ts.map +1 -0
- package/dist/src/lib/agent-hook-state.js +136 -0
- package/dist/src/lib/agent-receiver.d.ts +26 -0
- package/dist/src/lib/agent-receiver.d.ts.map +1 -0
- package/dist/src/lib/agent-receiver.js +44 -0
- package/dist/src/lib/api.d.ts.map +1 -1
- package/dist/src/lib/api.js +28 -1
- package/dist/src/lib/claude-settings.d.ts +82 -0
- package/dist/src/lib/claude-settings.d.ts.map +1 -0
- package/dist/src/lib/claude-settings.js +271 -0
- package/dist/src/lib/claude-settings.test.d.ts +2 -0
- package/dist/src/lib/claude-settings.test.d.ts.map +1 -0
- package/dist/src/lib/claude-settings.test.js +193 -0
- package/dist/src/lib/config.d.ts +23 -0
- package/dist/src/lib/config.d.ts.map +1 -1
- package/dist/src/lib/config.js +14 -0
- package/dist/src/lib/format.d.ts +6 -0
- package/dist/src/lib/format.d.ts.map +1 -0
- package/dist/src/lib/format.js +19 -0
- package/dist/src/lib/git.d.ts +3 -0
- package/dist/src/lib/git.d.ts.map +1 -0
- package/dist/src/lib/git.js +30 -0
- package/dist/src/lib/repo-guidance.d.ts +40 -0
- package/dist/src/lib/repo-guidance.d.ts.map +1 -0
- package/dist/src/lib/repo-guidance.js +123 -0
- package/dist/src/lib/validators.d.ts +69 -0
- package/dist/src/lib/validators.d.ts.map +1 -1
- package/dist/src/lib/validators.js +69 -0
- package/package.json +7 -5
package/dist/src/lib/config.d.ts
CHANGED
|
@@ -1,8 +1,25 @@
|
|
|
1
1
|
export declare const ENVIRONMENTS: {
|
|
2
2
|
readonly prod: "https://clocktopus.app";
|
|
3
|
+
readonly staging: "https://staging.clocktopus.app";
|
|
3
4
|
readonly dev: "http://localhost:3000";
|
|
4
5
|
};
|
|
5
6
|
export type Environment = keyof typeof ENVIRONMENTS;
|
|
7
|
+
/**
|
|
8
|
+
* Bookkeeping for the agent telemetry setup.
|
|
9
|
+
*
|
|
10
|
+
* Deliberately does **not** hold the ingest token. The token has to live in
|
|
11
|
+
* `~/.claude/settings.json` for Claude Code's exporter to read it, and a
|
|
12
|
+
* second copy here would be a second thing to leak and a second thing to
|
|
13
|
+
* fall out of date. What is kept is only what setup cannot recover later:
|
|
14
|
+
* the row id needed to revoke, and the prefix needed to display.
|
|
15
|
+
*/
|
|
16
|
+
type AgentConfig = {
|
|
17
|
+
tokenId?: string;
|
|
18
|
+
tokenPrefix?: string;
|
|
19
|
+
endpoint?: string;
|
|
20
|
+
/** ISO timestamp — `agent doctor` compares it against settings.json. */
|
|
21
|
+
configuredAt?: string;
|
|
22
|
+
};
|
|
6
23
|
export declare function setRuntimeEnvironment(env: Environment): void;
|
|
7
24
|
export declare function getEnvironment(): Environment;
|
|
8
25
|
export declare function getApiUrl(): string;
|
|
@@ -10,4 +27,10 @@ export declare function getToken(): string | undefined;
|
|
|
10
27
|
export declare function setToken(token: string): void;
|
|
11
28
|
export declare function clearToken(): void;
|
|
12
29
|
export declare function isLoggedIn(): boolean;
|
|
30
|
+
export declare function getAgentConfig(): AgentConfig;
|
|
31
|
+
export declare function setAgentConfig(next: AgentConfig): void;
|
|
32
|
+
export declare function clearAgentConfig(): void;
|
|
33
|
+
/** Where the config lives, so `agent doctor` can point at a real file. */
|
|
34
|
+
export declare function getConfigPath(): string;
|
|
35
|
+
export {};
|
|
13
36
|
//# sourceMappingURL=config.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../../src/lib/config.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,YAAY
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../../src/lib/config.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,YAAY;;;;CAIf,CAAC;AAEX,MAAM,MAAM,WAAW,GAAG,MAAM,OAAO,YAAY,CAAC;AAEpD;;;;;;;;GAQG;AACH,KAAK,WAAW,GAAG;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,wEAAwE;IACxE,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,CAAC;AAcF,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,WAAW,GAAG,IAAI,CAE5D;AAED,wBAAgB,cAAc,IAAI,WAAW,CAO5C;AAED,wBAAgB,SAAS,IAAI,MAAM,CASlC;AAED,wBAAgB,QAAQ,IAAI,MAAM,GAAG,SAAS,CAE7C;AAED,wBAAgB,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAE5C;AAED,wBAAgB,UAAU,IAAI,IAAI,CAEjC;AAED,wBAAgB,UAAU,IAAI,OAAO,CAEpC;AAED,wBAAgB,cAAc,IAAI,WAAW,CAE5C;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,WAAW,GAAG,IAAI,CAEtD;AAED,wBAAgB,gBAAgB,IAAI,IAAI,CAEvC;AAED,0EAA0E;AAC1E,wBAAgB,aAAa,IAAI,MAAM,CAEtC"}
|
package/dist/src/lib/config.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import Conf from "conf";
|
|
2
2
|
export const ENVIRONMENTS = {
|
|
3
3
|
prod: "https://clocktopus.app",
|
|
4
|
+
staging: "https://staging.clocktopus.app",
|
|
4
5
|
dev: "http://localhost:3000",
|
|
5
6
|
};
|
|
6
7
|
const config = new Conf({
|
|
@@ -40,3 +41,16 @@ export function clearToken() {
|
|
|
40
41
|
export function isLoggedIn() {
|
|
41
42
|
return !!getToken();
|
|
42
43
|
}
|
|
44
|
+
export function getAgentConfig() {
|
|
45
|
+
return config.get("agent") ?? {};
|
|
46
|
+
}
|
|
47
|
+
export function setAgentConfig(next) {
|
|
48
|
+
config.set("agent", { ...getAgentConfig(), ...next });
|
|
49
|
+
}
|
|
50
|
+
export function clearAgentConfig() {
|
|
51
|
+
config.delete("agent");
|
|
52
|
+
}
|
|
53
|
+
/** Where the config lives, so `agent doctor` can point at a real file. */
|
|
54
|
+
export function getConfigPath() {
|
|
55
|
+
return config.path;
|
|
56
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** "3 minutes ago" / "never". Null-safe because most of these fields are. */
|
|
2
|
+
export declare function formatAgo(value: string | null | undefined): string;
|
|
3
|
+
export declare function microUsdToDisplay(microUsd: number): string;
|
|
4
|
+
/** Pads a label so the value column lines up in the status blocks. */
|
|
5
|
+
export declare function labelled(label: string, value: string, width?: number): string;
|
|
6
|
+
//# sourceMappingURL=format.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"format.d.ts","sourceRoot":"","sources":["../../../src/lib/format.ts"],"names":[],"mappings":"AAEA,6EAA6E;AAC7E,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,CAOlE;AAED,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAE1D;AAED,sEAAsE;AACtE,wBAAgB,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,SAAK,GAAG,MAAM,CAEzE"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { formatDistanceToNowStrict, parseISO } from "date-fns";
|
|
2
|
+
/** "3 minutes ago" / "never". Null-safe because most of these fields are. */
|
|
3
|
+
export function formatAgo(value) {
|
|
4
|
+
if (!value)
|
|
5
|
+
return "never";
|
|
6
|
+
try {
|
|
7
|
+
return formatDistanceToNowStrict(parseISO(value), { addSuffix: true });
|
|
8
|
+
}
|
|
9
|
+
catch {
|
|
10
|
+
return value;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
export function microUsdToDisplay(microUsd) {
|
|
14
|
+
return `$${(microUsd / 1_000_000).toFixed(2)}`;
|
|
15
|
+
}
|
|
16
|
+
/** Pads a label so the value column lines up in the status blocks. */
|
|
17
|
+
export function labelled(label, value, width = 14) {
|
|
18
|
+
return ` ${label.padEnd(width)}${value}`;
|
|
19
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"git.d.ts","sourceRoot":"","sources":["../../../src/lib/git.ts"],"names":[],"mappings":"AA0BA,wBAAgB,mBAAmB,CAAC,GAAG,SAAgB,GAAG,MAAM,GAAG,IAAI,CAEtE;AAED,wBAAgB,eAAe,CAAC,GAAG,SAAgB,GAAG,OAAO,CAE5D"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
2
|
+
/**
|
|
3
|
+
* Just enough git to work out which repository the user is standing in.
|
|
4
|
+
*
|
|
5
|
+
* The CLI deliberately does not parse the remote — `apps/cli` is published
|
|
6
|
+
* standalone and cannot import `@repo/core`, so a parser here would be a
|
|
7
|
+
* second implementation destined to drift from the receiver's. The raw
|
|
8
|
+
* remote goes to the server, which owns the one definition of a supported
|
|
9
|
+
* remote.
|
|
10
|
+
*/
|
|
11
|
+
function git(cwd, args) {
|
|
12
|
+
try {
|
|
13
|
+
const output = execFileSync("git", args, {
|
|
14
|
+
cwd,
|
|
15
|
+
encoding: "utf8",
|
|
16
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
17
|
+
timeout: 2000,
|
|
18
|
+
}).trim();
|
|
19
|
+
return output || undefined;
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return undefined;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
export function getRepositoryRemote(cwd = process.cwd()) {
|
|
26
|
+
return git(cwd, ["remote", "get-url", "origin"]) ?? null;
|
|
27
|
+
}
|
|
28
|
+
export function isGitRepository(cwd = process.cwd()) {
|
|
29
|
+
return git(cwd, ["rev-parse", "--is-inside-work-tree"]) === "true";
|
|
30
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { RepoStatusResponse } from "./validators.js";
|
|
2
|
+
/**
|
|
3
|
+
* The half of the setup that is not on this machine.
|
|
4
|
+
*
|
|
5
|
+
* Configuring the exporter and the hook makes agent sessions *arrive*. It
|
|
6
|
+
* does not make them *mean* anything, and the two ways that fails are
|
|
7
|
+
* unrelated to each other and to everything `agent setup` writes locally:
|
|
8
|
+
*
|
|
9
|
+
* - The repository is not attached to a project, so spend lands in the
|
|
10
|
+
* unattributed row. Purely a Clocktopus-side mapping; no webhook affects
|
|
11
|
+
* it either way.
|
|
12
|
+
* - The push webhook is not delivering, so there are no human time entries
|
|
13
|
+
* to compare the spend against. Note this does *not* stop agent→commit
|
|
14
|
+
* links: the SessionEnd hook resolves the commit range locally and posts
|
|
15
|
+
* it. What is missing is the other side of the comparison, and a cost
|
|
16
|
+
* without the hours it bought is not a smaller answer — it is not one.
|
|
17
|
+
*
|
|
18
|
+
* Reported as two separate verdicts, because they send you to two
|
|
19
|
+
* different places, and a single "not set up" would send you to neither.
|
|
20
|
+
*/
|
|
21
|
+
export type RepoCheck = {
|
|
22
|
+
label: string;
|
|
23
|
+
ok: boolean | "warn";
|
|
24
|
+
detail: string;
|
|
25
|
+
fix?: string;
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Fetches readiness for the repository the CLI is standing in.
|
|
29
|
+
*
|
|
30
|
+
* Returns null when there is nothing to ask about — outside a git tree, or
|
|
31
|
+
* when the server is unreachable. A failure here must never fail `setup`:
|
|
32
|
+
* the local configuration it just wrote is valid regardless.
|
|
33
|
+
*/
|
|
34
|
+
export declare function fetchRepoStatus(cwd?: string): Promise<RepoStatusResponse | null>;
|
|
35
|
+
export declare function attributionCheck(status: RepoStatusResponse): RepoCheck;
|
|
36
|
+
export declare function commitLaneCheck(status: RepoStatusResponse): RepoCheck;
|
|
37
|
+
export declare function repoChecks(status: RepoStatusResponse): RepoCheck[];
|
|
38
|
+
/** The block `agent setup` prints after writing the local configuration. */
|
|
39
|
+
export declare function renderRepoBlock(status: RepoStatusResponse): string[];
|
|
40
|
+
//# sourceMappingURL=repo-guidance.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"repo-guidance.d.ts","sourceRoot":"","sources":["../../../src/lib/repo-guidance.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAO1D;;;;;;;;;;;;;;;;;;GAkBG;AAEH,MAAM,MAAM,SAAS,GAAG;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,EAAE,EAAE,OAAO,GAAG,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,CAAC;CACd,CAAC;AAEF;;;;;;GAMG;AACH,wBAAsB,eAAe,CACnC,GAAG,SAAgB,GAClB,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC,CAapC;AAED,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,kBAAkB,GAAG,SAAS,CAqDtE;AAED,wBAAgB,eAAe,CAAC,MAAM,EAAE,kBAAkB,GAAG,SAAS,CAqCrE;AAED,wBAAgB,UAAU,CAAC,MAAM,EAAE,kBAAkB,GAAG,SAAS,EAAE,CAElE;AAED,4EAA4E;AAC5E,wBAAgB,eAAe,CAAC,MAAM,EAAE,kBAAkB,GAAG,MAAM,EAAE,CAepE"}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { ApiError, get } from "./api.js";
|
|
2
|
+
import { getApiUrl } from "./config.js";
|
|
3
|
+
import { formatAgo } from "./format.js";
|
|
4
|
+
import { getRepositoryRemote } from "./git.js";
|
|
5
|
+
import { RepoStatusSchema } from "./validators.js";
|
|
6
|
+
/**
|
|
7
|
+
* Fetches readiness for the repository the CLI is standing in.
|
|
8
|
+
*
|
|
9
|
+
* Returns null when there is nothing to ask about — outside a git tree, or
|
|
10
|
+
* when the server is unreachable. A failure here must never fail `setup`:
|
|
11
|
+
* the local configuration it just wrote is valid regardless.
|
|
12
|
+
*/
|
|
13
|
+
export async function fetchRepoStatus(cwd = process.cwd()) {
|
|
14
|
+
const remote = getRepositoryRemote(cwd);
|
|
15
|
+
if (!remote)
|
|
16
|
+
return null;
|
|
17
|
+
try {
|
|
18
|
+
const data = await get(`/api/agent/repo-status?repositoryUrl=${encodeURIComponent(remote)}`);
|
|
19
|
+
return RepoStatusSchema.parse(data);
|
|
20
|
+
}
|
|
21
|
+
catch (error) {
|
|
22
|
+
if (error instanceof ApiError)
|
|
23
|
+
return null;
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
export function attributionCheck(status) {
|
|
28
|
+
const { attribution, repository } = status;
|
|
29
|
+
const where = repository?.displayUrl ?? "this repository";
|
|
30
|
+
switch (attribution.reason) {
|
|
31
|
+
case "attributed":
|
|
32
|
+
return {
|
|
33
|
+
label: "Project",
|
|
34
|
+
ok: true,
|
|
35
|
+
detail: attribution.projectNames[0] ?? "attached",
|
|
36
|
+
};
|
|
37
|
+
case "no_matching_project":
|
|
38
|
+
return {
|
|
39
|
+
label: "Project",
|
|
40
|
+
ok: false,
|
|
41
|
+
detail: `${where} is not attached to any project`,
|
|
42
|
+
fix: "Attach it under Projects → (your project) → Repositories.\n" +
|
|
43
|
+
" Until then this repo's agent spend is recorded, but lands in\n" +
|
|
44
|
+
" the unattributed row instead of against a client.",
|
|
45
|
+
};
|
|
46
|
+
case "ambiguous_project":
|
|
47
|
+
return {
|
|
48
|
+
label: "Project",
|
|
49
|
+
ok: false,
|
|
50
|
+
// Refusing to guess is the designed behaviour, not a bug — billing
|
|
51
|
+
// one client for another's work is worse than billing nobody.
|
|
52
|
+
detail: `${where} is attached to ${attribution.projectNames.length} projects (${attribution.projectNames.join(", ")})`,
|
|
53
|
+
fix: "Clocktopus will not guess which client to bill, so these sessions\n" +
|
|
54
|
+
" stay unattributed. Remove the repository from all but one project.",
|
|
55
|
+
};
|
|
56
|
+
case "unsupported_remote":
|
|
57
|
+
return {
|
|
58
|
+
label: "Project",
|
|
59
|
+
ok: "warn",
|
|
60
|
+
detail: "remote is not GitHub or Bitbucket",
|
|
61
|
+
fix: "Spend from this repository is recorded but cannot be attributed —\n" +
|
|
62
|
+
" only GitHub and Bitbucket remotes resolve to a project.",
|
|
63
|
+
};
|
|
64
|
+
case "no_repository":
|
|
65
|
+
return {
|
|
66
|
+
label: "Project",
|
|
67
|
+
ok: "warn",
|
|
68
|
+
detail: "not a git repository",
|
|
69
|
+
fix: "Run 'clocktopus agent setup' inside a project checkout to check attribution.",
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
export function commitLaneCheck(status) {
|
|
74
|
+
const dashboard = getApiUrl().replace(/\/$/, "");
|
|
75
|
+
switch (status.commitLane.status) {
|
|
76
|
+
case "delivering":
|
|
77
|
+
return {
|
|
78
|
+
label: "Commit lane",
|
|
79
|
+
ok: true,
|
|
80
|
+
detail: `last commit received ${formatAgo(status.commitLane.lastCommitAt)}`,
|
|
81
|
+
};
|
|
82
|
+
case "no_secret":
|
|
83
|
+
return {
|
|
84
|
+
label: "Commit lane",
|
|
85
|
+
ok: false,
|
|
86
|
+
detail: "no webhook secret — no commits are reaching Clocktopus",
|
|
87
|
+
fix: `Create one at ${dashboard}/settings/webhooks, then add a push\n` +
|
|
88
|
+
` webhook to this repository. Guide:\n` +
|
|
89
|
+
` ${dashboard}/docs/guides/setup-github-webhook`,
|
|
90
|
+
};
|
|
91
|
+
case "no_delivery":
|
|
92
|
+
return {
|
|
93
|
+
label: "Commit lane",
|
|
94
|
+
ok: false,
|
|
95
|
+
detail: "no commit has ever arrived from this repository",
|
|
96
|
+
fix: "Agent spend will be recorded here, but with no human time beside it\n" +
|
|
97
|
+
" the true-cost comparison has only one side. Add a push webhook:\n" +
|
|
98
|
+
` URL ${status.webhookUrl}\n` +
|
|
99
|
+
" Events push\n" +
|
|
100
|
+
` Secret ${dashboard}/settings/webhooks\n` +
|
|
101
|
+
" The URL is per person, not per repository — on a shared repo\n" +
|
|
102
|
+
" each teammate adds their own.",
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
export function repoChecks(status) {
|
|
107
|
+
return [attributionCheck(status), commitLaneCheck(status)];
|
|
108
|
+
}
|
|
109
|
+
/** The block `agent setup` prints after writing the local configuration. */
|
|
110
|
+
export function renderRepoBlock(status) {
|
|
111
|
+
const lines = [];
|
|
112
|
+
const heading = status.repository?.displayUrl ?? "this directory";
|
|
113
|
+
lines.push("");
|
|
114
|
+
lines.push(`Repository ${heading}`);
|
|
115
|
+
lines.push("");
|
|
116
|
+
for (const check of repoChecks(status)) {
|
|
117
|
+
const symbol = check.ok === true ? "✓" : check.ok === "warn" ? "⚠" : "✗";
|
|
118
|
+
lines.push(` ${symbol} ${check.label.padEnd(12)}${check.detail}`);
|
|
119
|
+
if (check.ok !== true && check.fix)
|
|
120
|
+
lines.push(` ${check.fix}`);
|
|
121
|
+
}
|
|
122
|
+
return lines;
|
|
123
|
+
}
|
|
@@ -36,4 +36,73 @@ export declare const TokenErrorResponseSchema: z.ZodObject<{
|
|
|
36
36
|
}, z.core.$strip>;
|
|
37
37
|
export type TokenResponse = z.infer<typeof TokenResponseSchema>;
|
|
38
38
|
export type TokenErrorResponse = z.infer<typeof TokenErrorResponseSchema>;
|
|
39
|
+
export declare const IngestTokenSchema: z.ZodObject<{
|
|
40
|
+
tokenId: z.ZodString;
|
|
41
|
+
token: z.ZodString;
|
|
42
|
+
tokenPrefix: z.ZodString;
|
|
43
|
+
endpoint: z.ZodString;
|
|
44
|
+
}, z.core.$strip>;
|
|
45
|
+
export declare const AgentEndpointSchema: z.ZodObject<{
|
|
46
|
+
endpoint: z.ZodString;
|
|
47
|
+
}, z.core.$strip>;
|
|
48
|
+
export declare const AgentStatusSchema: z.ZodObject<{
|
|
49
|
+
endpoint: z.ZodString;
|
|
50
|
+
window: z.ZodObject<{
|
|
51
|
+
since: z.ZodString;
|
|
52
|
+
sessions: z.ZodNumber;
|
|
53
|
+
unattributedSessions: z.ZodNumber;
|
|
54
|
+
costMicroUsd: z.ZodNumber;
|
|
55
|
+
}, z.core.$strip>;
|
|
56
|
+
hookLane: z.ZodObject<{
|
|
57
|
+
lastSessionStartedAt: z.ZodNullable<z.ZodString>;
|
|
58
|
+
repositoryUrl: z.ZodNullable<z.ZodString>;
|
|
59
|
+
gitBranch: z.ZodNullable<z.ZodString>;
|
|
60
|
+
cwd: z.ZodNullable<z.ZodString>;
|
|
61
|
+
attributed: z.ZodNullable<z.ZodBoolean>;
|
|
62
|
+
}, z.core.$strip>;
|
|
63
|
+
metricsLane: z.ZodObject<{
|
|
64
|
+
lastExportReceivedAt: z.ZodNullable<z.ZodString>;
|
|
65
|
+
lastModel: z.ZodNullable<z.ZodString>;
|
|
66
|
+
}, z.core.$strip>;
|
|
67
|
+
tokens: z.ZodArray<z.ZodObject<{
|
|
68
|
+
id: z.ZodString;
|
|
69
|
+
name: z.ZodNullable<z.ZodString>;
|
|
70
|
+
tokenPrefix: z.ZodString;
|
|
71
|
+
lastUsedAt: z.ZodNullable<z.ZodString>;
|
|
72
|
+
usageCount: z.ZodNumber;
|
|
73
|
+
createdAt: z.ZodString;
|
|
74
|
+
}, z.core.$strip>>;
|
|
75
|
+
}, z.core.$strip>;
|
|
76
|
+
export declare const RepoStatusSchema: z.ZodObject<{
|
|
77
|
+
repository: z.ZodNullable<z.ZodObject<{
|
|
78
|
+
provider: z.ZodString;
|
|
79
|
+
ownerRepo: z.ZodString;
|
|
80
|
+
displayUrl: z.ZodString;
|
|
81
|
+
}, z.core.$strip>>;
|
|
82
|
+
attribution: z.ZodObject<{
|
|
83
|
+
reason: z.ZodEnum<{
|
|
84
|
+
attributed: "attributed";
|
|
85
|
+
no_repository: "no_repository";
|
|
86
|
+
unsupported_remote: "unsupported_remote";
|
|
87
|
+
no_matching_project: "no_matching_project";
|
|
88
|
+
ambiguous_project: "ambiguous_project";
|
|
89
|
+
}>;
|
|
90
|
+
projectId: z.ZodNullable<z.ZodString>;
|
|
91
|
+
projectNames: z.ZodArray<z.ZodString>;
|
|
92
|
+
}, z.core.$strip>;
|
|
93
|
+
commitLane: z.ZodObject<{
|
|
94
|
+
status: z.ZodEnum<{
|
|
95
|
+
delivering: "delivering";
|
|
96
|
+
no_secret: "no_secret";
|
|
97
|
+
no_delivery: "no_delivery";
|
|
98
|
+
}>;
|
|
99
|
+
lastCommitAt: z.ZodNullable<z.ZodString>;
|
|
100
|
+
hasWebhookSecret: z.ZodBoolean;
|
|
101
|
+
}, z.core.$strip>;
|
|
102
|
+
ready: z.ZodBoolean;
|
|
103
|
+
webhookUrl: z.ZodString;
|
|
104
|
+
}, z.core.$strip>;
|
|
105
|
+
export type IngestTokenResponse = z.infer<typeof IngestTokenSchema>;
|
|
106
|
+
export type AgentStatusResponse = z.infer<typeof AgentStatusSchema>;
|
|
107
|
+
export type RepoStatusResponse = z.infer<typeof RepoStatusSchema>;
|
|
39
108
|
//# sourceMappingURL=validators.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validators.d.ts","sourceRoot":"","sources":["../../../src/lib/validators.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,eAAO,MAAM,UAAU;;;;;;;;;iBASrB,CAAC;AAEH,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,UAAU,CAAC,CAAC;AAEtD,eAAO,MAAM,wBAAwB;;;;;;;iBAOnC,CAAC;AAEH,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CAAC;AAE1E,eAAO,MAAM,mBAAmB;;;;iBAI9B,CAAC;AAEH,eAAO,MAAM,wBAAwB;;;;;;;;;iBASnC,CAAC;AAEH,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAChE,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CAAC"}
|
|
1
|
+
{"version":3,"file":"validators.d.ts","sourceRoot":"","sources":["../../../src/lib/validators.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,eAAO,MAAM,UAAU;;;;;;;;;iBASrB,CAAC;AAEH,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,UAAU,CAAC,CAAC;AAEtD,eAAO,MAAM,wBAAwB;;;;;;;iBAOnC,CAAC;AAEH,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CAAC;AAE1E,eAAO,MAAM,mBAAmB;;;;iBAI9B,CAAC;AAEH,eAAO,MAAM,wBAAwB;;;;;;;;;iBASnC,CAAC;AAEH,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAChE,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CAAC;AAO1E,eAAO,MAAM,iBAAiB;;;;;iBAM5B,CAAC;AAEH,eAAO,MAAM,mBAAmB;;iBAE9B,CAAC;AAEH,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA6B5B,CAAC;AAEH,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA0B3B,CAAC;AAEH,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AACpE,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AACpE,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC"}
|
|
@@ -32,3 +32,72 @@ export const TokenErrorResponseSchema = z.object({
|
|
|
32
32
|
]),
|
|
33
33
|
error_description: z.string().optional(),
|
|
34
34
|
});
|
|
35
|
+
// Agent telemetry (`clocktopus agent …`). The endpoint is always supplied by
|
|
36
|
+
// the server rather than known to the CLI: the receiver lives on its own
|
|
37
|
+
// Router whose URL differs per stage, so a baked-in default would be wrong
|
|
38
|
+
// everywhere but production.
|
|
39
|
+
export const IngestTokenSchema = z.object({
|
|
40
|
+
tokenId: z.string(),
|
|
41
|
+
/** Plaintext, returned exactly once — the server stores only a hash. */
|
|
42
|
+
token: z.string(),
|
|
43
|
+
tokenPrefix: z.string(),
|
|
44
|
+
endpoint: z.string(),
|
|
45
|
+
});
|
|
46
|
+
export const AgentEndpointSchema = z.object({
|
|
47
|
+
endpoint: z.string(),
|
|
48
|
+
});
|
|
49
|
+
export const AgentStatusSchema = z.object({
|
|
50
|
+
endpoint: z.string(),
|
|
51
|
+
window: z.object({
|
|
52
|
+
since: z.string(),
|
|
53
|
+
sessions: z.number(),
|
|
54
|
+
unattributedSessions: z.number(),
|
|
55
|
+
costMicroUsd: z.number(),
|
|
56
|
+
}),
|
|
57
|
+
hookLane: z.object({
|
|
58
|
+
lastSessionStartedAt: z.string().nullable(),
|
|
59
|
+
repositoryUrl: z.string().nullable(),
|
|
60
|
+
gitBranch: z.string().nullable(),
|
|
61
|
+
cwd: z.string().nullable(),
|
|
62
|
+
attributed: z.boolean().nullable(),
|
|
63
|
+
}),
|
|
64
|
+
metricsLane: z.object({
|
|
65
|
+
lastExportReceivedAt: z.string().nullable(),
|
|
66
|
+
lastModel: z.string().nullable(),
|
|
67
|
+
}),
|
|
68
|
+
tokens: z.array(z.object({
|
|
69
|
+
id: z.string(),
|
|
70
|
+
name: z.string().nullable(),
|
|
71
|
+
tokenPrefix: z.string(),
|
|
72
|
+
lastUsedAt: z.string().nullable(),
|
|
73
|
+
usageCount: z.number(),
|
|
74
|
+
createdAt: z.string(),
|
|
75
|
+
})),
|
|
76
|
+
});
|
|
77
|
+
export const RepoStatusSchema = z.object({
|
|
78
|
+
repository: z
|
|
79
|
+
.object({
|
|
80
|
+
provider: z.string(),
|
|
81
|
+
ownerRepo: z.string(),
|
|
82
|
+
displayUrl: z.string(),
|
|
83
|
+
})
|
|
84
|
+
.nullable(),
|
|
85
|
+
attribution: z.object({
|
|
86
|
+
reason: z.enum([
|
|
87
|
+
"attributed",
|
|
88
|
+
"no_repository",
|
|
89
|
+
"unsupported_remote",
|
|
90
|
+
"no_matching_project",
|
|
91
|
+
"ambiguous_project",
|
|
92
|
+
]),
|
|
93
|
+
projectId: z.string().nullable(),
|
|
94
|
+
projectNames: z.array(z.string()),
|
|
95
|
+
}),
|
|
96
|
+
commitLane: z.object({
|
|
97
|
+
status: z.enum(["delivering", "no_secret", "no_delivery"]),
|
|
98
|
+
lastCommitAt: z.string().nullable(),
|
|
99
|
+
hasWebhookSecret: z.boolean(),
|
|
100
|
+
}),
|
|
101
|
+
ready: z.boolean(),
|
|
102
|
+
webhookUrl: z.string(),
|
|
103
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@blastin-dev/clocktopus-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"clocktopus": "./dist/bin/clocktopus.js"
|
|
@@ -12,13 +12,14 @@
|
|
|
12
12
|
"commander": "^13.0.0",
|
|
13
13
|
"conf": "^13.0.0",
|
|
14
14
|
"date-fns": "4.1.0",
|
|
15
|
-
"zod": "4.
|
|
15
|
+
"zod": "4.4.3"
|
|
16
16
|
},
|
|
17
17
|
"devDependencies": {
|
|
18
18
|
"@types/node": "22.15.3",
|
|
19
|
-
"eslint": "
|
|
19
|
+
"eslint": "9.37.0",
|
|
20
20
|
"eslint-plugin-import-x": "^4.16.1",
|
|
21
21
|
"typescript": "5.9.2",
|
|
22
|
+
"vitest": "^4.1.5",
|
|
22
23
|
"@repo/eslint-config": "0.0.0",
|
|
23
24
|
"@repo/prettier-config": "0.1.0",
|
|
24
25
|
"@repo/typescript-config": "0.0.0"
|
|
@@ -28,8 +29,9 @@
|
|
|
28
29
|
"build": "tsc",
|
|
29
30
|
"dev": "tsc --watch",
|
|
30
31
|
"check-types": "tsc --noEmit",
|
|
32
|
+
"test": "vitest run",
|
|
31
33
|
"lint": "eslint",
|
|
32
|
-
"format": "prettier --check . --ignore-path ../../.gitignore",
|
|
33
|
-
"format:fix": "prettier --write . --ignore-path ../../.gitignore"
|
|
34
|
+
"format": "prettier --check . --ignore-path ../../.gitignore --ignore-path ../../.prettierignore",
|
|
35
|
+
"format:fix": "prettier --write . --ignore-path ../../.gitignore --ignore-path ../../.prettierignore"
|
|
34
36
|
}
|
|
35
37
|
}
|