@qodly/gentrace 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,324 @@
1
+ # Gentrace CLI (`gentrace`)
2
+
3
+ Command-line bridge between **[git-ai](https://github.com/git-ai/git-ai)** and the **Gentrace** API: after each commit it can send **`commit_attribution`** telemetry (AI vs human line stats, diff context, and optional prompts) to `POST /v1/telemetry`.
4
+
5
+ Package name on npm: **`@qodly/gentrace`** · binary: **`gentrace`**
6
+
7
+ ---
8
+
9
+ ## Prerequisites
10
+
11
+ | Requirement | Purpose |
12
+ |-------------|---------|
13
+ | **Git** | Repository detection, hooks, diff, commit metadata. |
14
+ | **[git-ai](https://github.com/git-ai/git-ai)** | Records attribution for commits; the CLI reads git-ai output to build telemetry. |
15
+ | **[Bun](https://bun.sh)** | The published `gentrace` entrypoint (`dist/cli.js`) uses `#!/usr/bin/env bun` — install Bun and ensure it is on your `PATH`. |
16
+ | **Gentrace account** | Sign in to your Gentrace dashboard, create an **access token** with permission to ingest telemetry (see below). |
17
+
18
+ Optional:
19
+
20
+ - **curl** / **bash** — used by `gentrace install-git-ai` for the official git-ai installer on macOS/Linux.
21
+
22
+ ---
23
+
24
+ ## Install
25
+
26
+ ### From npm (recommended)
27
+
28
+ ```bash
29
+ npm install -g @qodly/gentrace
30
+ ```
31
+
32
+ Verify:
33
+
34
+ ```bash
35
+ gentrace --version
36
+ ```
37
+
38
+ ### One-off run with npx
39
+
40
+ ```bash
41
+ npx @qodly/gentrace@latest doctor
42
+ ```
43
+
44
+ ### From this monorepo (contributors)
45
+
46
+ From the repository root (requires [Bun](https://bun.sh)):
47
+
48
+ ```bash
49
+ bun run --filter @qodly/gentrace install:global
50
+ ```
51
+
52
+ Or from `apps/cli`:
53
+
54
+ ```bash
55
+ cd apps/cli && bun run install:global
56
+ ```
57
+
58
+ That script builds the CLI and links the `gentrace` command globally. See `scripts/install-global.sh` for flags (e.g. `--no-build`).
59
+
60
+ ---
61
+
62
+ ## Getting started
63
+
64
+ ### 1. Install git-ai
65
+
66
+ If `git-ai` is not installed:
67
+
68
+ ```bash
69
+ gentrace install-git-ai
70
+ ```
71
+
72
+ This runs the installer from [usegitai.com](https://usegitai.com). On Windows (native), the command prints a PowerShell one-liner; on WSL/macOS/Linux it offers the `curl … \| bash` flow. After installation, reload your shell or add `~/.git-ai/bin` to `PATH` if the binary is not found.
73
+
74
+ Confirm:
75
+
76
+ ```bash
77
+ gentrace doctor
78
+ ```
79
+
80
+ `doctor` checks `git`, `git-ai`, API configuration, and API reachability.
81
+
82
+ ### 2. Create an API token in the dashboard
83
+
84
+ 1. Open your Gentrace web app (for example `https://gentrace.4d-ps.com`) and sign in.
85
+ 2. Go to **Settings** (personal access tokens), e.g. `https://gentrace.4d-ps.com/settings`.
86
+ 3. Create **New access token**.
87
+ 4. Grant at least **`telemetry:ingest`** (or **`*`**) so `gentrace publish` can send batches. Tokens used only for ingest often include `telemetry:ingest`; `gentrace doctor` also calls `GET /v1/auth/permissions`, which requires sufficient IAM on the token (see doctor output if something fails).
88
+
89
+ Copy the token **once** — it may not be shown again.
90
+
91
+ ### 3. Configure the API key (and optional API URL)
92
+
93
+ **Interactive** (saved to `~/.config/gentrace/config.json`):
94
+
95
+ ```bash
96
+ gentrace doctor
97
+ ```
98
+
99
+ If no key is set, `doctor` can prompt for it when a TTY is available.
100
+
101
+ **Explicit**:
102
+
103
+ ```bash
104
+ gentrace config set apiKey 'YOUR_TOKEN_HERE'
105
+ ```
106
+
107
+ Default API base URL is `https://gentrace.4d-ps.com/api` (paths such as `/v1/telemetry` are appended to this base). Override if needed:
108
+
109
+ ```bash
110
+ gentrace config set apiUrl 'https://your-host.example.com/api'
111
+ ```
112
+
113
+ Or use environment variables (highest precedence for URL/key):
114
+
115
+ ```bash
116
+ export GENTRACE_API_KEY='YOUR_TOKEN_HERE'
117
+ export GENTRACE_API_URL='https://your-host.example.com/api'
118
+ ```
119
+
120
+ ### 4. Verify the setup
121
+
122
+ ```bash
123
+ gentrace doctor
124
+ ```
125
+
126
+ You should see checks for git, git-ai, API key, HTTP health, and token IAMs for telemetry ingest.
127
+
128
+ ### 5. Install the post-commit hook (per repository)
129
+
130
+ In each Git repo where you want automatic publishes after every commit:
131
+
132
+ ```bash
133
+ cd /path/to/your/repo
134
+ gentrace hooks install
135
+ ```
136
+
137
+ Optional second argument: path to the repo root:
138
+
139
+ ```bash
140
+ gentrace hooks install /path/to/your/repo
141
+ ```
142
+
143
+ The hook runs **`gentrace publish --daemon`** in the background (`nohup`) so commits are not blocked. Logs go to `.git/gentrace-publish.log`.
144
+
145
+ Ensure **`gentrace`** is on `PATH` when Git runs hooks (same as in your interactive shell), or set **`GENTRACE_BIN`** to the full path of the binary in the environment used by GUI Git clients / CI if needed.
146
+
147
+ ### 6. Manual publish (optional test)
148
+
149
+ From a repo with git-ai data for `HEAD`:
150
+
151
+ ```bash
152
+ gentrace publish
153
+ ```
154
+
155
+ Optional working directory:
156
+
157
+ ```bash
158
+ gentrace publish /path/to/repo
159
+ ```
160
+
161
+ ---
162
+
163
+ ## Command reference
164
+
165
+ Global notes:
166
+
167
+ - **`gentrace --help`** / **`-h`** — short usage summary.
168
+ - **`gentrace --version`** / **`-V`** — CLI version string.
169
+ - **Verbose logging** — place **`--verbose`**, **`--debug`**, or **`-v`** before the subcommand (or on supported commands such as `publish` / `doctor`) to log HTTP details to stderr. Same effect: **`GENTRACE_DEBUG=1`** (or `true`).
170
+
171
+ ---
172
+
173
+ ### `gentrace publish`
174
+
175
+ Collects Git AI attribution and git metadata for **HEAD** in a repository, builds a telemetry batch, and **POST**s it to **`{apiUrl}/v1/telemetry`**.
176
+
177
+ **Usage**
178
+
179
+ ```text
180
+ gentrace publish [--verbose|--debug|-v] [--daemon|--background] [cwd]
181
+ ```
182
+
183
+ | Argument / flag | Description |
184
+ |-----------------|-------------|
185
+ | **`cwd`** | Optional path to the repository root (defaults to current working directory). |
186
+ | **`--daemon`** / **`--background`** | After a delay, run collection in a style suited to hooks: waits **`GENTRACE_PUBLISH_DELAY_MS`** ms if set, otherwise **2500** ms, so git-ai can finish writing stats. Then collects and sends. |
187
+ | **Verbose flags** | Extra stderr logs (URL, redacted headers, body preview, response). |
188
+
189
+ **Behavior highlights**
190
+
191
+ - Skips the run if the repository id (origin URL, or path fallback) matches **`skipRepositories`** (see `gentrace skip`).
192
+ - Requires **git-ai** on PATH; exits with a hint to run **`gentrace install-git-ai`** if missing.
193
+ - Resolves API key from **`GENTRACE_API_KEY`** or config; may prompt once in a TTY.
194
+ - **200**: prints accepted count when present in JSON body.
195
+ - **409**: treated as already recorded (“Commit … already recorded”).
196
+ - **401** / **403**: auth error; suggests checking the token and **`telemetry:ingest`**.
197
+
198
+ ---
199
+
200
+ ### `gentrace doctor`
201
+
202
+ Validates local tools and API connectivity.
203
+
204
+ **Usage**
205
+
206
+ ```text
207
+ gentrace doctor [--verbose|--debug|-v]
208
+ ```
209
+
210
+ **Checks (in order)**
211
+
212
+ 1. **`git`** on PATH.
213
+ 2. **`git-ai`** on PATH.
214
+ 3. **API key** — from env or config; may prompt and save in a TTY.
215
+ 4. **`GET {apiUrl}/health`** — unauthenticated liveness.
216
+ 5. **`GET {apiUrl}/v1/auth/permissions`** with **`X-API-Key`** — confirms token works and IAMs include **`telemetry:ingest`** or **`*`** for publish.
217
+
218
+ Exits **`0`** if all checks pass, **`1`** otherwise.
219
+
220
+ ---
221
+
222
+ ### `gentrace install-git-ai`
223
+
224
+ If git-ai is already installed, prints the version and exits.
225
+
226
+ Otherwise (on Unix/WSL) asks for confirmation, then runs:
227
+
228
+ `curl -sSL https://usegitai.com/install.sh | bash`
229
+
230
+ On Windows (non-WSL), prints the recommended PowerShell install command instead.
231
+
232
+ ---
233
+
234
+ ### `gentrace hooks install` / `gentrace hooks uninstall`
235
+
236
+ Manage a **post-commit** shell hook under **`.git/hooks/post-commit`**.
237
+
238
+ **Usage**
239
+
240
+ ```text
241
+ gentrace hooks install [cwd]
242
+ gentrace hooks uninstall [cwd]
243
+ ```
244
+
245
+ | Subcommand | Description |
246
+ |------------|-------------|
247
+ | **`install`** | Ensures `.git/hooks` exists, then appends or creates a block marked `# gentrace-cli post-commit hook` that runs **`nohup gentrace publish --daemon`** (with **`GENTRACE_BIN`** defaulting to `gentrace`). If an older gentrace hook exists, it may be replaced with the async version. May prompt for API key once. |
248
+ | **`uninstall`** | Removes only the gentrace-marked section; deletes the file if nothing else remains (except a bare `#!/bin/sh`). |
249
+
250
+ **`cwd`** — optional path to the repo root (default: current directory).
251
+
252
+ ---
253
+
254
+ ### `gentrace config`
255
+
256
+ Read or write **`~/.config/gentrace/config.json`**.
257
+
258
+ **Usage**
259
+
260
+ ```text
261
+ gentrace config # print full JSON (apiKey masked)
262
+ gentrace config get [key] # get one key or all if omitted in some forms
263
+ gentrace config set <key> <value> # set scalar keys (see below)
264
+ gentrace config unset <key> # reset key to default
265
+ gentrace config <key> # same as: config get <key>
266
+ ```
267
+
268
+ **Keys**
269
+
270
+ | Key | Type | Description |
271
+ |-----|------|-------------|
272
+ | **`apiUrl`** | string | API base URL (default includes `/api` path; see source `DEFAULTS`). |
273
+ | **`apiKey`** | string | Secret token (`get` shows a masked value). |
274
+ | **`includePrompts`** | boolean | Set with `gentrace config set includePrompts true` or `false` — when true, publish may attach prompt payloads derived from the authorship log (heavier / more sensitive). |
275
+ | **`skipRepositories`** | string[] | Prefer managing via **`gentrace skip`**; patterns match repository id (origin URL or path), with simple `*` glob support. |
276
+
277
+ ---
278
+
279
+ ### `gentrace skip`
280
+
281
+ Manage the **skip list** so **`gentrace publish`** (and the hook) no-op for matching repositories.
282
+
283
+ **Usage**
284
+
285
+ ```text
286
+ gentrace skip add <pattern>
287
+ gentrace skip remove <pattern>
288
+ gentrace skip list
289
+ ```
290
+
291
+ | Subcommand | Description |
292
+ |------------|-------------|
293
+ | **`add`** | Append a pattern if not already present. |
294
+ | **`remove`** | Remove an exact pattern entry. |
295
+ | **`list`** | Print all patterns (or a message if empty). |
296
+
297
+ **Matching** — `*` matches any repository; otherwise exact match, or glob if the pattern contains `*`.
298
+
299
+ ---
300
+
301
+ ## Configuration file
302
+
303
+ - **Path:** `~/.config/gentrace/config.json` (or **`$XDG_CONFIG_HOME/gentrace/config.json`** when set).
304
+ - **Permissions:** written with mode **0600**.
305
+ - **Precedence:** **`GENTRACE_API_URL`** / **`GENTRACE_API_KEY`** override saved values when set.
306
+
307
+ ---
308
+
309
+ ## Environment variables
310
+
311
+ | Variable | Purpose |
312
+ |----------|---------|
313
+ | **`GENTRACE_API_KEY`** | API token (overrides config file). |
314
+ | **`GENTRACE_API_URL`** | API base URL (overrides config file). |
315
+ | **`GENTRACE_DEBUG`** | Set to `1` or `true` for verbose stderr logs. |
316
+ | **`GENTRACE_PUBLISH_DELAY_MS`** | Milliseconds to wait before collecting when using **`--daemon`** (default **2500** when daemon; **`0`** when not daemon unless this env is set). |
317
+ | **`GENTRACE_BIN`** | Used by the post-commit hook: full path to **`gentrace`** if it is not on the default `PATH` for non-interactive Git. |
318
+
319
+ ---
320
+
321
+ ## Related documentation
322
+
323
+ - Monorepo CLI overview: [`../../docs/cli.md`](../../docs/cli.md)
324
+ - API / webhooks / IAM concepts: repository root **`README.md`** and **`ARCHITECTURE.md`**
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env bun
2
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,149 @@
1
+ #!/usr/bin/env bun
2
+ import { configGet, configSet, configUnset } from './commands/config.js';
3
+ import { doctor } from './commands/doctor.js';
4
+ import { hooksInstall, hooksUninstall } from './commands/hooks.js';
5
+ import { installGitAi } from './commands/install-git-ai.js';
6
+ import { publish } from './commands/publish.js';
7
+ import { skipAdd, skipList, skipRemove } from './commands/skip.js';
8
+ import { enableDebug, VERBOSE_FLAGS } from './lib/debug.js';
9
+ const VERSION = '2.0.0';
10
+ const HELP = `\
11
+ gentrace - Git AI → Gentrace telemetry bridge
12
+
13
+ Usage:
14
+ gentrace <command> [options]
15
+
16
+ Commands:
17
+ publish Collect Git AI data for HEAD and send to Gentrace API
18
+ Use --daemon from hooks: non-blocking + delay before collect
19
+ doctor Check git, git-ai, API key, and API reachability
20
+ install-git-ai Install the git-ai CLI if not already present
21
+
22
+ hooks install Install post-commit hook in current repo
23
+ hooks uninstall Remove post-commit hook from current repo
24
+
25
+ config Show all configuration
26
+ config get <key> Show a config value
27
+ config set <key> <v> Set a config value
28
+ config unset <key> Reset a config value to default
29
+
30
+ skip add <pattern> Add repo to skip list
31
+ skip remove <pat> Remove repo from skip list
32
+ skip list List skipped repos
33
+
34
+ --version, -V Print version
35
+ --help, -h Print this help
36
+
37
+ Place --verbose, --debug, or -v before a command or on publish/doctor for HTTP
38
+ and payload traces on stderr. Same effect: GENTRACE_DEBUG=1.
39
+
40
+ Environment:
41
+ GENTRACE_API_KEY API key (overrides config file)
42
+ GENTRACE_API_URL API base URL (overrides config file)
43
+ GENTRACE_DEBUG Set to 1/true for verbose stderr logs
44
+ GENTRACE_PUBLISH_DELAY_MS Milliseconds to wait before reading git-ai (default 2500 with --daemon)
45
+ GENTRACE_BIN Used by post-commit hook; path to gentrace binary
46
+
47
+ Config file: ~/.config/gentrace/config.json
48
+ `;
49
+ async function main() {
50
+ let args = process.argv.slice(2);
51
+ while (args[0] && VERBOSE_FLAGS.has(args[0])) {
52
+ enableDebug();
53
+ args = args.slice(1);
54
+ }
55
+ const cmd = args[0];
56
+ if (!cmd || cmd === '--help' || cmd === '-h') {
57
+ console.log(HELP);
58
+ return;
59
+ }
60
+ if (cmd === '--version' || cmd === '-V') {
61
+ console.log(`gentrace ${VERSION}`);
62
+ return;
63
+ }
64
+ switch (cmd) {
65
+ case 'publish':
66
+ await publish(args.slice(1));
67
+ break;
68
+ case 'doctor':
69
+ await doctor(args.slice(1));
70
+ break;
71
+ case 'install-git-ai':
72
+ await installGitAi();
73
+ break;
74
+ case 'hooks': {
75
+ const sub = args[1];
76
+ if (sub === 'install') {
77
+ await hooksInstall(args[2]);
78
+ }
79
+ else if (sub === 'uninstall') {
80
+ await hooksUninstall(args[2]);
81
+ }
82
+ else {
83
+ console.error('Usage: gentrace hooks install|uninstall');
84
+ process.exit(1);
85
+ }
86
+ break;
87
+ }
88
+ case 'config': {
89
+ const sub = args[1];
90
+ if (!sub) {
91
+ configGet();
92
+ }
93
+ else if (sub === 'get') {
94
+ configGet(args[2]);
95
+ }
96
+ else if (sub === 'set') {
97
+ if (!args[2] || !args[3]) {
98
+ console.error('Usage: gentrace config set <key> <value>');
99
+ process.exit(1);
100
+ }
101
+ configSet(args[2], args[3]);
102
+ }
103
+ else if (sub === 'unset') {
104
+ if (!args[2]) {
105
+ console.error('Usage: gentrace config unset <key>');
106
+ process.exit(1);
107
+ }
108
+ configUnset(args[2]);
109
+ }
110
+ else {
111
+ // Treat as `config get <key>`
112
+ configGet(sub);
113
+ }
114
+ break;
115
+ }
116
+ case 'skip': {
117
+ const sub = args[1];
118
+ if (sub === 'add') {
119
+ if (!args[2]) {
120
+ console.error('Usage: gentrace skip add <pattern>');
121
+ process.exit(1);
122
+ }
123
+ skipAdd(args[2]);
124
+ }
125
+ else if (sub === 'remove') {
126
+ if (!args[2]) {
127
+ console.error('Usage: gentrace skip remove <pattern>');
128
+ process.exit(1);
129
+ }
130
+ skipRemove(args[2]);
131
+ }
132
+ else if (sub === 'list') {
133
+ skipList();
134
+ }
135
+ else {
136
+ console.error('Usage: gentrace skip add|remove|list');
137
+ process.exit(1);
138
+ }
139
+ break;
140
+ }
141
+ default:
142
+ console.error(`Unknown command: ${cmd}\nRun 'gentrace --help' for usage.`);
143
+ process.exit(1);
144
+ }
145
+ }
146
+ main().catch((err) => {
147
+ console.error(err instanceof Error ? err.message : err);
148
+ process.exit(1);
149
+ });
@@ -0,0 +1,3 @@
1
+ export declare function configGet(key?: string): void;
2
+ export declare function configSet(key: string, value: string): void;
3
+ export declare function configUnset(key: string): void;
@@ -0,0 +1,69 @@
1
+ import { loadConfig, saveConfig } from '../lib/config.js';
2
+ const VALID_KEYS = [
3
+ 'apiUrl',
4
+ 'apiKey',
5
+ 'includePrompts',
6
+ 'skipRepositories',
7
+ ];
8
+ export function configGet(key) {
9
+ const cfg = loadConfig();
10
+ if (!key) {
11
+ const display = { ...cfg, apiKey: cfg.apiKey ? maskKey(cfg.apiKey) : '(not set)' };
12
+ console.log(JSON.stringify(display, null, 2));
13
+ return;
14
+ }
15
+ if (!isValidKey(key)) {
16
+ console.error(`Unknown config key: ${key}`);
17
+ console.error(`Valid keys: ${VALID_KEYS.join(', ')}`);
18
+ process.exit(1);
19
+ }
20
+ const val = cfg[key];
21
+ if (key === 'apiKey') {
22
+ console.log(val ? maskKey(val) : '(not set)');
23
+ }
24
+ else {
25
+ console.log(typeof val === 'object' ? JSON.stringify(val, null, 2) : String(val));
26
+ }
27
+ }
28
+ export function configSet(key, value) {
29
+ if (!isValidKey(key)) {
30
+ console.error(`Unknown config key: ${key}`);
31
+ console.error(`Valid keys: ${VALID_KEYS.join(', ')}`);
32
+ process.exit(1);
33
+ }
34
+ const patch = {};
35
+ if (key === 'includePrompts') {
36
+ patch.includePrompts = value === 'true';
37
+ }
38
+ else if (key === 'skipRepositories') {
39
+ const current = loadConfig();
40
+ patch.skipRepositories = [...new Set([...current.skipRepositories, value])];
41
+ }
42
+ else {
43
+ patch[key] = value;
44
+ }
45
+ saveConfig(patch);
46
+ console.log(`Set ${key}`);
47
+ }
48
+ export function configUnset(key) {
49
+ if (!isValidKey(key)) {
50
+ console.error(`Unknown config key: ${key}`);
51
+ process.exit(1);
52
+ }
53
+ const defaults = {
54
+ apiUrl: 'https://gentrace.4d-ps.com/api',
55
+ apiKey: '',
56
+ includePrompts: false,
57
+ skipRepositories: [],
58
+ };
59
+ saveConfig({ [key]: defaults[key] });
60
+ console.log(`Unset ${key} (reverted to default)`);
61
+ }
62
+ function isValidKey(key) {
63
+ return VALID_KEYS.includes(key);
64
+ }
65
+ function maskKey(key) {
66
+ if (key.length <= 8)
67
+ return '****';
68
+ return `${key.slice(0, 4)}…${key.slice(-4)}`;
69
+ }
@@ -0,0 +1 @@
1
+ export declare function doctor(argv?: string[]): Promise<void>;