@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.
Files changed (39) hide show
  1. package/README.md +124 -43
  2. package/dist/agent-instructions.d.ts +2 -1
  3. package/dist/agents.d.ts +36 -0
  4. package/dist/agents.js +27 -0
  5. package/dist/doctor.d.ts +6 -1
  6. package/dist/doctor.js +9 -6
  7. package/dist/enrich-store-event.js +2 -1
  8. package/dist/groundtrack.js +9 -195
  9. package/dist/session-import/cli.d.ts +21 -0
  10. package/dist/session-import/cli.js +335 -0
  11. package/dist/session-import/client.d.ts +13 -0
  12. package/dist/session-import/client.js +73 -0
  13. package/dist/session-import/codex.d.ts +37 -0
  14. package/dist/session-import/codex.js +134 -0
  15. package/dist/session-import/discovery.d.ts +22 -0
  16. package/dist/session-import/discovery.js +188 -0
  17. package/dist/session-import/prune.d.ts +5 -0
  18. package/dist/session-import/prune.js +120 -0
  19. package/dist/session-import/receipts.d.ts +20 -0
  20. package/dist/session-import/receipts.js +44 -0
  21. package/dist/session-import/redaction.d.ts +6 -0
  22. package/dist/session-import/redaction.js +61 -0
  23. package/dist/session-import/types.d.ts +28 -0
  24. package/dist/session-import/types.js +1 -0
  25. package/dist/setup/agents.d.ts +59 -0
  26. package/dist/setup/agents.js +160 -0
  27. package/dist/setup/cli.d.ts +16 -0
  28. package/dist/setup/cli.js +332 -0
  29. package/dist/setup/config.d.ts +9 -0
  30. package/dist/setup/config.js +143 -0
  31. package/dist/setup/connections.d.ts +26 -0
  32. package/dist/setup/connections.js +210 -0
  33. package/dist/setup/files.d.ts +18 -0
  34. package/dist/setup/files.js +104 -0
  35. package/dist/setup/plan.d.ts +30 -0
  36. package/dist/setup/plan.js +324 -0
  37. package/package.json +29 -2
  38. package/src/agents.ts +37 -0
  39. package/templates/AGENTS.md +2 -1
package/README.md CHANGED
@@ -1,56 +1,137 @@
1
1
  # @groundtrack/hooks
2
2
 
3
- Lifecycle hook utilities for Groundtrack MCP clients.
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
- Install globally so the `groundtrack-hook` command is available to Claude Code,
6
- Codex, and Cursor hook configurations:
6
+ ```sh
7
+ npx --yes --package @groundtrack/hooks@latest groundtrack setup
8
+ ```
7
9
 
8
- ```bash
9
- npm install -g @groundtrack/hooks
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
- ## Doctor CLI
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 same package installs a deterministic `groundtrack` CLI. Run it from a
15
- repository root:
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
- ```bash
18
- groundtrack doctor
19
- groundtrack doctor --fix --client codex
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
- `doctor` checks the installed package/runtime version, discovers the actual
23
- Groundtrack MCP server alias, validates Claude/Codex/Cursor hook configuration,
24
- checks the managed `AGENTS.md` and `CLAUDE.md` instructions, and simulates a
25
- hook invocation. It exits non-zero when repair is needed.
26
-
27
- Pass `--client claude`, `--client codex`, or `--client cursor` when repairing a
28
- repository. This limits MCP alias discovery and configuration changes to that
29
- client. If more than one client is detected, `doctor --fix` refuses to guess
30
- which one to modify.
31
-
32
- The Doctor verifies local configuration and enrichment behavior, but it cannot
33
- observe whether a coding-agent host loaded or trusted a project hook. A
34
- `search_experiences` MCP response is the authoritative live check: Groundtrack
35
- returns a `hook_context_missing` warning when hook-injected session context is
36
- absent. In Codex, use `/hooks` to review and trust the Groundtrack hook when that
37
- warning appears.
38
-
39
- `doctor --fix` previews the diagnosis, asks before changing anything, updates
40
- only Groundtrack-owned hook entries and versioned managed instruction blocks,
41
- then repeats the verification. Use `--yes` for an explicitly authorized
42
- unattended repair. Existing surrounding instructions and unrelated hooks are
43
- preserved; malformed JSON and symbolic links are reported instead of
44
- overwritten.
45
-
46
- The CLI compares its package version with npm. Canonical hook configuration and
47
- instruction templates ship inside that versioned package, so repair does not
48
- depend on an LLM or download arbitrary configuration text.
49
-
50
- `templates/AGENTS.md` is the single source of truth for the managed repository
51
- policy. After changing it, run `pnpm sync:agent-instructions` to regenerate the
52
- setup-guide example; tests reject drift between the generated copy and the
53
- packaged template.
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 = "claude" | "codex" | "cursor" | "opencode";
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;
@@ -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
- export type Client = "claude" | "codex" | "cursor";
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
- return (/^@AGENTS\.md$/m.test(content) ||
239
- managedContentIsCurrent(content, AGENT_INSTRUCTION_BODY) ||
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 === "claude" || host === "cursor" ? host : "codex";
153
+ const client = isAgent(host) ? host : "codex";
153
154
  const root = repositoryRoot(cwd);
154
155
  toolInput.agentInstructionsVersion = root ? detectAgentInstructionsVersion(root, client) : null;
155
156
  }
@@ -1,199 +1,13 @@
1
1
  #!/usr/bin/env node
2
- import { createInterface } from "node:readline/promises";
3
- import { spawnSync } from "node:child_process";
4
- import { homedir } from "node:os";
5
- import { resolve } from "node:path";
6
- import { stdin, stdout } from "node:process";
7
- import { compareVersions, runDoctor } from "./doctor.js";
8
- import { packageVersion } from "./version.js";
9
- function usage() {
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
- printResult(repaired);
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 {};