@groundtrack/hooks 0.1.7 → 0.1.9
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 -43
- package/dist/agent-instructions.d.ts +2 -1
- package/dist/agents.d.ts +36 -0
- package/dist/agents.js +27 -0
- package/dist/doctor.d.ts +6 -1
- package/dist/doctor.js +9 -6
- package/dist/enrich-store-event.js +2 -1
- package/dist/groundtrack.js +9 -195
- package/dist/session-import/cli.d.ts +21 -0
- package/dist/session-import/cli.js +335 -0
- package/dist/session-import/client.d.ts +13 -0
- package/dist/session-import/client.js +73 -0
- package/dist/session-import/codex.d.ts +37 -0
- package/dist/session-import/codex.js +134 -0
- package/dist/session-import/discovery.d.ts +22 -0
- package/dist/session-import/discovery.js +188 -0
- package/dist/session-import/prune.d.ts +5 -0
- package/dist/session-import/prune.js +120 -0
- package/dist/session-import/receipts.d.ts +20 -0
- package/dist/session-import/receipts.js +44 -0
- package/dist/session-import/redaction.d.ts +6 -0
- package/dist/session-import/redaction.js +61 -0
- package/dist/session-import/types.d.ts +28 -0
- package/dist/session-import/types.js +1 -0
- package/dist/setup/agents.d.ts +59 -0
- package/dist/setup/agents.js +160 -0
- package/dist/setup/cli.d.ts +16 -0
- package/dist/setup/cli.js +332 -0
- package/dist/setup/config.d.ts +9 -0
- package/dist/setup/config.js +143 -0
- package/dist/setup/connections.d.ts +26 -0
- package/dist/setup/connections.js +210 -0
- package/dist/setup/files.d.ts +18 -0
- package/dist/setup/files.js +104 -0
- package/dist/setup/plan.d.ts +30 -0
- package/dist/setup/plan.js +324 -0
- package/package.json +29 -2
- package/src/agents.ts +37 -0
- package/templates/AGENTS.md +2 -1
package/README.md
CHANGED
|
@@ -1,56 +1,137 @@
|
|
|
1
1
|
# @groundtrack/hooks
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Set up Groundtrack across Claude Code, Codex, Cursor, and OpenCode in one flow.
|
|
4
|
+
Node.js 18 or newer is required.
|
|
4
5
|
|
|
5
|
-
|
|
6
|
-
|
|
6
|
+
```sh
|
|
7
|
+
npx --yes --package @groundtrack/hooks@latest groundtrack setup
|
|
8
|
+
```
|
|
7
9
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
+
Choose the agents you use, review the changes, and finish any sign-in or hook
|
|
11
|
+
approval shown for each agent. The picker detects both machine installations
|
|
12
|
+
and project configuration. Your choices are remembered; adding Codex later
|
|
13
|
+
preserves your working Claude and Cursor connections.
|
|
14
|
+
|
|
15
|
+
```sh
|
|
16
|
+
groundtrack setup --agent codex
|
|
17
|
+
groundtrack setup --agent claude --agent codex --agent cursor --yes
|
|
18
|
+
groundtrack setup --all-detected --yes
|
|
19
|
+
groundtrack setup --agent codex --dry-run
|
|
20
|
+
groundtrack status
|
|
21
|
+
groundtrack status --json
|
|
10
22
|
```
|
|
11
23
|
|
|
12
|
-
|
|
24
|
+
`--agent` is repeatable. `--client` and `claude-code` are accepted aliases.
|
|
25
|
+
Setup configures the current project; it does not activate Groundtrack in every
|
|
26
|
+
repository. Use `--repo PATH` to choose a project and `--url URL` for another
|
|
27
|
+
Groundtrack endpoint. With no explicit targets, an interactive terminal shows
|
|
28
|
+
the picker. An unattended `--yes` run needs a previously saved selection or
|
|
29
|
+
explicit `--agent` / `--all-detected`; it never falls back to all known agents.
|
|
30
|
+
An empty selection or cancellation makes no changes.
|
|
31
|
+
|
|
32
|
+
The same package provides `groundtrack-hook`. Setup installs this shared runtime
|
|
33
|
+
once if needed for Claude, Codex, or Cursor. OpenCode uses
|
|
34
|
+
`@groundtrack/opencode-plugin` instead. Repeat runs use the installed runtime
|
|
35
|
+
without querying npm for updates on every invocation.
|
|
36
|
+
|
|
37
|
+
The CLI validates JSON/JSONC and TOML, preserves unrelated settings and comments,
|
|
38
|
+
reuses existing Groundtrack aliases, and backs up changed files before applying
|
|
39
|
+
atomic writes. Backups and selections live under
|
|
40
|
+
`$XDG_CONFIG_HOME/groundtrack` (default `~/.config/groundtrack`). Backups may
|
|
41
|
+
contain existing private configuration, so their directory and files are private.
|
|
42
|
+
A failure rolls back completed writes where possible and reports the backup
|
|
43
|
+
location. Malformed configuration, symbolic links, disabled connections/hooks,
|
|
44
|
+
and endpoint conflicts are reported for the affected agent. Other independent
|
|
45
|
+
selected agents can still be configured. Adding agents does not uninstall others.
|
|
46
|
+
|
|
47
|
+
## Connection and verification
|
|
48
|
+
|
|
49
|
+
A local check verifies configuration, not a live authenticated host. Status
|
|
50
|
+
reports `configured` and `activation: unverified`; it never infers a connection
|
|
51
|
+
from a generated file. Each agent must load the connection and its hooks.
|
|
52
|
+
|
|
53
|
+
Interactive setup checks native sign-in requirements first. Claude Code must
|
|
54
|
+
approve a new project MCP server before OAuth; setup shows that action instead
|
|
55
|
+
of attempting a login that will fail. Codex can authenticate the selected endpoint
|
|
56
|
+
with a one-command configuration override while project and hook trust remain
|
|
57
|
+
explicit user actions. Existing Claude/OpenCode authentication is reused when
|
|
58
|
+
the client reports it, and successful logins are reflected in the final summary.
|
|
59
|
+
Use `--connect` to retry native sign-in for selected clients. Cursor and
|
|
60
|
+
desktop-only installations show their UI steps. In unattended runs, setup prints instructions and never blocks waiting
|
|
61
|
+
for OAuth. `--json` returns structured results without configuration contents or
|
|
62
|
+
credentials; use `--yes` to apply an explicitly selected setup.
|
|
63
|
+
|
|
64
|
+
Codex requires review and trust of the Groundtrack hook through `/hooks` or its
|
|
65
|
+
hook approval UI. New Codex setups use `.codex/hooks.json`; existing inline
|
|
66
|
+
hooks in `.codex/config.toml` are recognized and repaired without duplicating
|
|
67
|
+
them. Claude's `CLAUDE.md` imports the shared `AGENTS.md` policy. The plugin is
|
|
68
|
+
added to the existing OpenCode JSON/JSONC configuration.
|
|
69
|
+
|
|
70
|
+
In each selected agent, ask:
|
|
71
|
+
|
|
72
|
+
> Verify Groundtrack's connection and hook context with search_experiences.
|
|
73
|
+
|
|
74
|
+
The returned hook-context and instruction-version warnings determine whether
|
|
75
|
+
live activation needs attention. No synthetic engineering event is required.
|
|
76
|
+
|
|
77
|
+
## Doctor compatibility
|
|
78
|
+
|
|
79
|
+
`groundtrack doctor` is an alias for `status`; `groundtrack doctor --fix` uses the
|
|
80
|
+
same setup and repair flow. Existing single-client commands continue to work:
|
|
81
|
+
|
|
82
|
+
```sh
|
|
83
|
+
groundtrack doctor --fix --client codex
|
|
84
|
+
```
|
|
13
85
|
|
|
14
|
-
The
|
|
15
|
-
|
|
86
|
+
The lower-level `runDoctor` API remains for existing consumers. New setup uses
|
|
87
|
+
client-specific plans for MCP, hooks/plugins, and instructions, so it can preview
|
|
88
|
+
and repair several selected agents safely.
|
|
16
89
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
90
|
+
## Import Codex sessions
|
|
91
|
+
|
|
92
|
+
Import completed Codex sessions from `CODEX_HOME` (default `~/.codex`) with an API key
|
|
93
|
+
created in Groundtrack Settings. The default scope is the current Git repository;
|
|
94
|
+
subagents and files modified in the last five minutes are excluded.
|
|
95
|
+
|
|
96
|
+
```sh
|
|
97
|
+
GROUNDTRACK_API_KEY=... groundtrack import --dry-run
|
|
98
|
+
GROUNDTRACK_API_KEY=... groundtrack import --yes
|
|
99
|
+
GROUNDTRACK_API_KEY=... groundtrack import --all-repos --include-unscoped --yes
|
|
20
100
|
```
|
|
21
101
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
102
|
+
The command streams source files through a deterministic privacy filter before upload.
|
|
103
|
+
Reasoning, system/developer instructions, turn context other than the model name, and
|
|
104
|
+
recognized credentials are removed locally. Uploads are sequential and retry only
|
|
105
|
+
transient failures. Successful imports are recorded atomically under
|
|
106
|
+
`$XDG_CONFIG_HOME/groundtrack`; receipts are scoped to the endpoint, API-key fingerprint,
|
|
107
|
+
visibility, source hash, and canonicalization version. Use `--json` for one machine-readable
|
|
108
|
+
result object and `--url` for a preview or self-hosted deployment.
|
|
109
|
+
|
|
110
|
+
`templates/AGENTS.md` is the source of truth for the managed repository policy.
|
|
111
|
+
After changing it, run `pnpm sync:agent-instructions`; tests reject drift between
|
|
112
|
+
the setup guide, this repository's managed `AGENTS.md` block, and the packaged template.
|
|
113
|
+
|
|
114
|
+
## Development
|
|
115
|
+
|
|
116
|
+
Agent IDs, names, aliases, event sources, and enrichment kinds are defined once in
|
|
117
|
+
`src/agents.ts`, exported as `@groundtrack/hooks/agents`. The CLI, web UI, hooks,
|
|
118
|
+
and server consume that catalog and its derived types. Keep it dependency-free
|
|
119
|
+
so browser bundles can load it without the Node-only hook runtime.
|
|
120
|
+
|
|
121
|
+
Build and test the CLI from the repository:
|
|
122
|
+
|
|
123
|
+
```sh
|
|
124
|
+
pnpm --filter @groundtrack/hooks build
|
|
125
|
+
node packages/hooks/dist/groundtrack.js setup --repo /path/to/test-project
|
|
126
|
+
pnpm --filter @groundtrack/hooks test
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
With Claude Code and Codex installed, also run the isolated native-client
|
|
130
|
+
preflight checks (they do not start OAuth or touch your normal agent profiles):
|
|
131
|
+
|
|
132
|
+
```sh
|
|
133
|
+
GROUNDTRACK_TEST_NATIVE_CLIENTS=1 pnpm --filter @groundtrack/hooks test
|
|
134
|
+
```
|
|
54
135
|
|
|
55
136
|
The `groundtrack-hook` command reads a pre-tool-use hook payload from standard input and enriches
|
|
56
137
|
Groundtrack `store_event` and `search_experiences` calls with:
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
import type { Agent } from "./agents.js";
|
|
1
2
|
/** Canonical managed Groundtrack policy, packaged with the CLI. */
|
|
2
3
|
export declare const AGENT_INSTRUCTION_BODY: string;
|
|
3
4
|
/** Version is derived from the canonical template marker; never define it separately. */
|
|
4
5
|
export declare const AGENT_INSTRUCTION_TEMPLATE_VERSION: number;
|
|
5
|
-
export type AgentInstructionsClient =
|
|
6
|
+
export type AgentInstructionsClient = Agent;
|
|
6
7
|
/** Detect the managed policy version available to a client in one repository. */
|
|
7
8
|
export declare function detectAgentInstructionsVersion(repoDir: string, client: AgentInstructionsClient): number | null;
|
package/dist/agents.d.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/** Shared by the CLI, browser UI, hooks, and server. Keep this module dependency-free. */
|
|
2
|
+
export declare const AGENTS: readonly [{
|
|
3
|
+
readonly id: "claude";
|
|
4
|
+
readonly name: "Claude Code";
|
|
5
|
+
readonly aliases: readonly ["claude-code"];
|
|
6
|
+
readonly source: "claude_code";
|
|
7
|
+
readonly enrichment: "command";
|
|
8
|
+
}, {
|
|
9
|
+
readonly id: "codex";
|
|
10
|
+
readonly name: "Codex";
|
|
11
|
+
readonly aliases: readonly [];
|
|
12
|
+
readonly source: "codex";
|
|
13
|
+
readonly enrichment: "command";
|
|
14
|
+
}, {
|
|
15
|
+
readonly id: "cursor";
|
|
16
|
+
readonly name: "Cursor";
|
|
17
|
+
readonly aliases: readonly [];
|
|
18
|
+
readonly source: "cursor";
|
|
19
|
+
readonly enrichment: "command";
|
|
20
|
+
}, {
|
|
21
|
+
readonly id: "opencode";
|
|
22
|
+
readonly name: "OpenCode";
|
|
23
|
+
readonly aliases: readonly [];
|
|
24
|
+
readonly source: "opencode";
|
|
25
|
+
readonly enrichment: "plugin";
|
|
26
|
+
}];
|
|
27
|
+
export type AgentDefinition = (typeof AGENTS)[number];
|
|
28
|
+
export type Agent = AgentDefinition["id"];
|
|
29
|
+
export type CommandHookAgent = Extract<AgentDefinition, {
|
|
30
|
+
enrichment: "command";
|
|
31
|
+
}>["id"];
|
|
32
|
+
export declare const AGENT_IDS: ("claude" | "codex" | "cursor" | "opencode")[];
|
|
33
|
+
export declare function isAgent(value: unknown): value is Agent;
|
|
34
|
+
export declare function resolveAgent(value: string): AgentDefinition | undefined;
|
|
35
|
+
export declare function agentFromClientName(value: string | null | undefined): AgentDefinition | undefined;
|
|
36
|
+
export declare function requiresHookRuntime(id: Agent): boolean;
|
package/dist/agents.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/** Shared by the CLI, browser UI, hooks, and server. Keep this module dependency-free. */
|
|
2
|
+
export const AGENTS = [
|
|
3
|
+
{
|
|
4
|
+
id: "claude",
|
|
5
|
+
name: "Claude Code",
|
|
6
|
+
aliases: ["claude-code"],
|
|
7
|
+
source: "claude_code",
|
|
8
|
+
enrichment: "command",
|
|
9
|
+
},
|
|
10
|
+
{ id: "codex", name: "Codex", aliases: [], source: "codex", enrichment: "command" },
|
|
11
|
+
{ id: "cursor", name: "Cursor", aliases: [], source: "cursor", enrichment: "command" },
|
|
12
|
+
{ id: "opencode", name: "OpenCode", aliases: [], source: "opencode", enrichment: "plugin" },
|
|
13
|
+
];
|
|
14
|
+
export const AGENT_IDS = AGENTS.map((agent) => agent.id);
|
|
15
|
+
export function isAgent(value) {
|
|
16
|
+
return AGENTS.some((agent) => agent.id === value);
|
|
17
|
+
}
|
|
18
|
+
export function resolveAgent(value) {
|
|
19
|
+
return AGENTS.find((agent) => agent.id === value || agent.aliases.some((alias) => alias === value));
|
|
20
|
+
}
|
|
21
|
+
export function agentFromClientName(value) {
|
|
22
|
+
const normalized = value?.trim().toLowerCase() ?? "";
|
|
23
|
+
return AGENTS.find((agent) => normalized.includes(agent.id));
|
|
24
|
+
}
|
|
25
|
+
export function requiresHookRuntime(id) {
|
|
26
|
+
return AGENTS.some((agent) => agent.id === id && agent.enrichment === "command");
|
|
27
|
+
}
|
package/dist/doctor.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
import type { CommandHookAgent } from "./agents.js";
|
|
2
|
+
export declare const CLAUDE_INSTRUCTION_BODY: string;
|
|
3
|
+
export type Client = CommandHookAgent;
|
|
2
4
|
type CheckStatus = "ok" | "advisory" | "issue" | "fixed" | "blocked";
|
|
3
5
|
export type DoctorCheck = {
|
|
4
6
|
id: string;
|
|
@@ -24,6 +26,9 @@ export type DoctorResult = {
|
|
|
24
26
|
changedFiles: string[];
|
|
25
27
|
healthy: boolean;
|
|
26
28
|
};
|
|
29
|
+
export declare function claudeInstructionsAreCurrent(content: string | null): boolean;
|
|
30
|
+
export declare function claudeUsesInlineGroundtrackInstructions(content: string | null): boolean;
|
|
31
|
+
export declare function upsertManagedContent(content: string | null, desired: string, migrateLegacyAgentInstructions?: boolean): string;
|
|
27
32
|
export declare function compareVersions(left: string, right: string): number;
|
|
28
33
|
export declare function runDoctor(options: DoctorOptions): DoctorResult;
|
|
29
34
|
export {};
|
package/dist/doctor.js
CHANGED
|
@@ -7,7 +7,7 @@ import { packageVersion } from "./version.js";
|
|
|
7
7
|
const HOOK_TEMPLATE_VERSION = 1;
|
|
8
8
|
const MANAGED_START = `<!-- groundtrack:start version=${AGENT_INSTRUCTION_TEMPLATE_VERSION} -->`;
|
|
9
9
|
const MANAGED_END = "<!-- groundtrack:end -->";
|
|
10
|
-
const CLAUDE_INSTRUCTION_BODY = `${MANAGED_START}
|
|
10
|
+
export const CLAUDE_INSTRUCTION_BODY = `${MANAGED_START}
|
|
11
11
|
@AGENTS.md
|
|
12
12
|
${MANAGED_END}`;
|
|
13
13
|
const UNMARKED_AGENT_INSTRUCTION_BODY = AGENT_INSTRUCTION_BODY.replace(MANAGED_START, "")
|
|
@@ -232,14 +232,17 @@ function managedContentIsCurrent(content, desired) {
|
|
|
232
232
|
const end = content.indexOf(MANAGED_END, start);
|
|
233
233
|
return start >= 0 && end >= 0 && content.slice(start, end + MANAGED_END.length) === desired;
|
|
234
234
|
}
|
|
235
|
-
function claudeInstructionsAreCurrent(content) {
|
|
235
|
+
export function claudeInstructionsAreCurrent(content) {
|
|
236
236
|
if (!content)
|
|
237
237
|
return false;
|
|
238
|
-
|
|
239
|
-
|
|
238
|
+
if (/^@AGENTS\.md\s*$/m.test(content)) {
|
|
239
|
+
const bridgeVersion = /<!-- groundtrack:start version=(\d+) -->/.exec(content)?.[1];
|
|
240
|
+
return (bridgeVersion === undefined || Number(bridgeVersion) >= AGENT_INSTRUCTION_TEMPLATE_VERSION);
|
|
241
|
+
}
|
|
242
|
+
return (managedContentIsCurrent(content, AGENT_INSTRUCTION_BODY) ||
|
|
240
243
|
content.includes(UNMARKED_AGENT_INSTRUCTION_BODY));
|
|
241
244
|
}
|
|
242
|
-
function claudeUsesInlineGroundtrackInstructions(content) {
|
|
245
|
+
export function claudeUsesInlineGroundtrackInstructions(content) {
|
|
243
246
|
return content?.includes(LEGACY_AGENT_SIGNATURE) ?? false;
|
|
244
247
|
}
|
|
245
248
|
function removeLegacyAgentInstructions(content) {
|
|
@@ -266,7 +269,7 @@ function removeLegacyAgentInstructions(content) {
|
|
|
266
269
|
}
|
|
267
270
|
return lines.join("\n");
|
|
268
271
|
}
|
|
269
|
-
function upsertManagedContent(content, desired, migrateLegacyAgentInstructions = false) {
|
|
272
|
+
export function upsertManagedContent(content, desired, migrateLegacyAgentInstructions = false) {
|
|
270
273
|
const existing = content ?? "";
|
|
271
274
|
const managed = /<!-- groundtrack:start version=\d+ -->[\s\S]*?<!-- groundtrack:end -->/;
|
|
272
275
|
if (managed.test(existing)) {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isAgent } from "./agents.js";
|
|
1
2
|
import { execFileSync } from "node:child_process";
|
|
2
3
|
import { closeSync, fstatSync, openSync, readSync } from "node:fs";
|
|
3
4
|
import { detectAgentInstructionsVersion, } from "./agent-instructions.js";
|
|
@@ -149,7 +150,7 @@ export function enrichInput(hookInput, clientOverride, tool = "store_event") {
|
|
|
149
150
|
toolInput.clientEventId = [host, hostSessionId, invocationId].filter(Boolean).join(":");
|
|
150
151
|
}
|
|
151
152
|
if (tool === "search_experiences") {
|
|
152
|
-
const client = host
|
|
153
|
+
const client = isAgent(host) ? host : "codex";
|
|
153
154
|
const root = repositoryRoot(cwd);
|
|
154
155
|
toolInput.agentInstructionsVersion = root ? detectAgentInstructionsVersion(root, client) : null;
|
|
155
156
|
}
|
package/dist/groundtrack.js
CHANGED
|
@@ -1,199 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
return `Groundtrack CLI
|
|
11
|
-
|
|
12
|
-
Usage:
|
|
13
|
-
groundtrack doctor [--fix] [--yes] [--json] [--client CLIENT] [--repo PATH]
|
|
14
|
-
groundtrack version [--json]
|
|
15
|
-
|
|
16
|
-
Commands:
|
|
17
|
-
doctor Diagnose Groundtrack hooks and managed agent instructions
|
|
18
|
-
doctor --fix Repair deterministic configuration drift
|
|
19
|
-
version Print the installed CLI/runtime version
|
|
20
|
-
|
|
21
|
-
Options:
|
|
22
|
-
--client Limit diagnosis and repair to claude, codex, or cursor
|
|
23
|
-
`;
|
|
24
|
-
}
|
|
25
|
-
function parseArguments(argv) {
|
|
26
|
-
if (argv[0] &&
|
|
27
|
-
!["doctor", "version", "--version"].includes(argv[0]) &&
|
|
28
|
-
!argv[0].startsWith("-")) {
|
|
29
|
-
throw new Error(`Unknown command: ${argv[0]}\n\n${usage()}`);
|
|
30
|
-
}
|
|
31
|
-
const command = argv[0] === "version" || argv[0] === "--version" ? "version" : "doctor";
|
|
32
|
-
let repoDir = process.cwd();
|
|
33
|
-
let homeDir = homedir();
|
|
34
|
-
let client;
|
|
35
|
-
for (let index = 0; index < argv.length; index += 1) {
|
|
36
|
-
if (argv[index] === "--repo" && argv[index + 1])
|
|
37
|
-
repoDir = resolve(argv[++index]);
|
|
38
|
-
if (argv[index] === "--home" && argv[index + 1])
|
|
39
|
-
homeDir = resolve(argv[++index]);
|
|
40
|
-
if (argv[index] === "--client") {
|
|
41
|
-
const value = argv[++index];
|
|
42
|
-
if (value !== "claude" && value !== "codex" && value !== "cursor") {
|
|
43
|
-
throw new Error("--client must be one of: claude, codex, cursor");
|
|
44
|
-
}
|
|
45
|
-
client = value;
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
return {
|
|
49
|
-
command,
|
|
50
|
-
fix: argv.includes("--fix"),
|
|
51
|
-
yes: argv.includes("--yes") || argv.includes("-y"),
|
|
52
|
-
json: argv.includes("--json"),
|
|
53
|
-
client,
|
|
54
|
-
repoDir,
|
|
55
|
-
homeDir,
|
|
56
|
-
};
|
|
57
|
-
}
|
|
58
|
-
function printResult(result, options = {}) {
|
|
59
|
-
const selectedClient = options.client ?? (result.clients.length === 1 ? result.clients[0] : null);
|
|
60
|
-
const fixHint = !options.showFixHint
|
|
61
|
-
? ""
|
|
62
|
-
: selectedClient
|
|
63
|
-
? ` Run: groundtrack doctor --fix --client ${selectedClient}`
|
|
64
|
-
: " Re-run with --client claude, --client codex, or --client cursor.";
|
|
65
|
-
stdout.write(`Groundtrack doctor ${result.version}\n`);
|
|
66
|
-
stdout.write(`Clients: ${result.clients.join(", ")}\n`);
|
|
67
|
-
stdout.write(`MCP aliases: ${result.aliases.join(", ")}\n\n`);
|
|
68
|
-
for (const check of result.checks) {
|
|
69
|
-
const icon = check.status === "ok"
|
|
70
|
-
? "✓"
|
|
71
|
-
: check.status === "advisory"
|
|
72
|
-
? "i"
|
|
73
|
-
: check.status === "fixed"
|
|
74
|
-
? "◆"
|
|
75
|
-
: check.status === "blocked"
|
|
76
|
-
? "×"
|
|
77
|
-
: "!";
|
|
78
|
-
stdout.write(`${icon} ${check.label}: ${check.detail}${check.file ? ` (${check.file})` : ""}\n`);
|
|
79
|
-
}
|
|
80
|
-
if (result.changedFiles.length > 0) {
|
|
81
|
-
stdout.write(`\nUpdated ${result.changedFiles.length} file(s).\n`);
|
|
82
|
-
}
|
|
83
|
-
stdout.write(result.healthy
|
|
84
|
-
? "\nGroundtrack local configuration is healthy. Live hook activation is verified by Groundtrack MCP calls.\n"
|
|
85
|
-
: `\nGroundtrack local configuration needs attention.${fixHint}\n`);
|
|
86
|
-
}
|
|
87
|
-
async function confirmFix() {
|
|
88
|
-
const prompt = createInterface({ input: stdin, output: stdout });
|
|
89
|
-
try {
|
|
90
|
-
const answer = await prompt.question("Apply deterministic Groundtrack repairs? [y/N] ");
|
|
91
|
-
return /^y(?:es)?$/i.test(answer.trim());
|
|
92
|
-
}
|
|
93
|
-
finally {
|
|
94
|
-
prompt.close();
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
async function latestPackageVersion() {
|
|
98
|
-
const override = process.env.GROUNDTRACK_DOCTOR_LATEST_VERSION;
|
|
99
|
-
if (override)
|
|
100
|
-
return override;
|
|
101
|
-
if (process.env.GROUNDTRACK_DOCTOR_OFFLINE === "1")
|
|
102
|
-
return null;
|
|
103
|
-
const controller = new AbortController();
|
|
104
|
-
const timeout = setTimeout(() => controller.abort(), 2_000);
|
|
105
|
-
try {
|
|
106
|
-
const response = await fetch("https://registry.npmjs.org/@groundtrack%2Fhooks/latest", {
|
|
107
|
-
signal: controller.signal,
|
|
108
|
-
headers: { accept: "application/json" },
|
|
109
|
-
});
|
|
110
|
-
if (!response.ok)
|
|
111
|
-
return null;
|
|
112
|
-
const payload = (await response.json());
|
|
113
|
-
return typeof payload.version === "string" ? payload.version : null;
|
|
114
|
-
}
|
|
115
|
-
catch {
|
|
116
|
-
return null;
|
|
117
|
-
}
|
|
118
|
-
finally {
|
|
119
|
-
clearTimeout(timeout);
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
function updatePackageAndRerun(version) {
|
|
123
|
-
const install = spawnSync("npm", ["install", "-g", `@groundtrack/hooks@${version}`], {
|
|
124
|
-
stdio: "inherit",
|
|
125
|
-
timeout: 120_000,
|
|
126
|
-
});
|
|
127
|
-
if (install.status !== 0)
|
|
128
|
-
return install.status ?? 1;
|
|
129
|
-
const rerun = spawnSync("groundtrack", process.argv.slice(2), {
|
|
130
|
-
stdio: "inherit",
|
|
131
|
-
env: {
|
|
132
|
-
...process.env,
|
|
133
|
-
GROUNDTRACK_DOCTOR_REEXEC: "1",
|
|
134
|
-
GROUNDTRACK_DOCTOR_LATEST_VERSION: version,
|
|
135
|
-
},
|
|
136
|
-
});
|
|
137
|
-
return rerun.status ?? 1;
|
|
138
|
-
}
|
|
139
|
-
async function main() {
|
|
140
|
-
const args = parseArguments(process.argv.slice(2));
|
|
141
|
-
if (process.argv.includes("--help") || process.argv.includes("-h")) {
|
|
142
|
-
stdout.write(usage());
|
|
143
|
-
return;
|
|
144
|
-
}
|
|
145
|
-
if (args.command === "version") {
|
|
146
|
-
const version = packageVersion();
|
|
147
|
-
stdout.write(args.json ? `${JSON.stringify({ version })}\n` : `${version}\n`);
|
|
148
|
-
return;
|
|
149
|
-
}
|
|
150
|
-
const base = {
|
|
151
|
-
repoDir: args.repoDir,
|
|
152
|
-
homeDir: args.homeDir,
|
|
153
|
-
clients: args.client ? [args.client] : undefined,
|
|
154
|
-
skipPackageInstall: process.env.GROUNDTRACK_DOCTOR_SKIP_INSTALL === "1",
|
|
155
|
-
latestVersion: await latestPackageVersion(),
|
|
156
|
-
};
|
|
157
|
-
const diagnosis = runDoctor(base);
|
|
158
|
-
if (!args.fix) {
|
|
159
|
-
if (args.json)
|
|
160
|
-
stdout.write(`${JSON.stringify(diagnosis, null, 2)}\n`);
|
|
161
|
-
else
|
|
162
|
-
printResult(diagnosis, { showFixHint: true, client: args.client });
|
|
163
|
-
process.exitCode = diagnosis.healthy ? 0 : 1;
|
|
164
|
-
return;
|
|
165
|
-
}
|
|
166
|
-
if (!args.client && diagnosis.clients.length > 1) {
|
|
167
|
-
throw new Error(`Multiple coding-agent clients detected: ${diagnosis.clients.join(", ")}. Re-run with --client claude, --client codex, or --client cursor so Doctor does not modify unrelated client configuration.`);
|
|
168
|
-
}
|
|
169
|
-
if (diagnosis.healthy) {
|
|
170
|
-
if (args.json)
|
|
171
|
-
stdout.write(`${JSON.stringify(diagnosis, null, 2)}\n`);
|
|
172
|
-
else
|
|
173
|
-
printResult(diagnosis);
|
|
174
|
-
return;
|
|
175
|
-
}
|
|
176
|
-
if (!args.json)
|
|
177
|
-
printResult(diagnosis);
|
|
178
|
-
if (!args.yes && !(await confirmFix())) {
|
|
179
|
-
stdout.write("No changes made.\n");
|
|
180
|
-
process.exitCode = 1;
|
|
181
|
-
return;
|
|
182
|
-
}
|
|
183
|
-
if (base.latestVersion &&
|
|
184
|
-
compareVersions(base.latestVersion, diagnosis.version) > 0 &&
|
|
185
|
-
process.env.GROUNDTRACK_DOCTOR_REEXEC !== "1") {
|
|
186
|
-
process.exitCode = updatePackageAndRerun(base.latestVersion);
|
|
187
|
-
return;
|
|
188
|
-
}
|
|
189
|
-
const repaired = runDoctor({ ...base, fix: true });
|
|
190
|
-
if (args.json)
|
|
191
|
-
stdout.write(`${JSON.stringify(repaired, null, 2)}\n`);
|
|
2
|
+
import { runCli } from "./setup/cli.js";
|
|
3
|
+
import { runImportCli } from "./session-import/cli.js";
|
|
4
|
+
const argv = process.argv.slice(2);
|
|
5
|
+
const operation = argv[0] === "import" ? runImportCli(argv.slice(1)) : runCli(argv);
|
|
6
|
+
operation.catch((error) => {
|
|
7
|
+
const message = error instanceof Error ? error.message : "Groundtrack command failed.";
|
|
8
|
+
if (process.argv.includes("--json"))
|
|
9
|
+
process.stdout.write(`${JSON.stringify({ error: message, applied: false })}\n`);
|
|
192
10
|
else
|
|
193
|
-
|
|
194
|
-
process.exitCode = repaired.healthy ? 0 : 1;
|
|
195
|
-
}
|
|
196
|
-
main().catch((error) => {
|
|
197
|
-
process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
|
|
11
|
+
process.stderr.write(`${message}\n`);
|
|
198
12
|
process.exitCode = 1;
|
|
199
13
|
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { ImportVisibility } from "./types.js";
|
|
2
|
+
export declare const IMPORT_USAGE = "Usage:\n groundtrack import [--agent codex] [--repo PATH | --all-repos] [--since DATE]\n [--include-subagents] [--include-unscoped]\n [--visibility private|organization] [--dry-run] [--yes] [--json]\n\nImport options:\n --agent codex Session format to discover (default: codex)\n --repo PATH Import sessions for this Git repository (default: current repository)\n --all-repos Import sessions carrying repository metadata\n --include-unscoped With --all-repos, also import sessions without repository metadata\n --since DATE Import sessions at or after an ISO timestamp or local YYYY-MM-DD\n --include-subagents Include subagent, forked-agent, and guardian sessions\n --visibility VALUE private (default) or organization\n --api-key KEY API key; prefer the GROUNDTRACK_API_KEY environment variable\n --url URL Groundtrack app or API URL (default: https://app.groundtrack.dev)\n --dry-run Discover and prune without authentication or network calls\n --yes, -y Upload without another confirmation\n --json Emit exactly one machine-readable result object\n";
|
|
3
|
+
type ImportArguments = Readonly<{
|
|
4
|
+
repoDir?: string;
|
|
5
|
+
homeDir?: string;
|
|
6
|
+
allRepositories: boolean;
|
|
7
|
+
includeUnscoped: boolean;
|
|
8
|
+
includeSubagents: boolean;
|
|
9
|
+
visibility: ImportVisibility;
|
|
10
|
+
since: Date | null;
|
|
11
|
+
dryRun: boolean;
|
|
12
|
+
yes: boolean;
|
|
13
|
+
json: boolean;
|
|
14
|
+
apiKey?: string;
|
|
15
|
+
apiBaseUrl: string;
|
|
16
|
+
help: boolean;
|
|
17
|
+
}>;
|
|
18
|
+
export declare function normalizeApiBaseUrl(value: string): string;
|
|
19
|
+
export declare function parseImportArguments(argv: readonly string[]): ImportArguments;
|
|
20
|
+
export declare function runImportCli(argv: readonly string[]): Promise<void>;
|
|
21
|
+
export {};
|