trackerctl 0.2.0 → 0.3.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 CHANGED
@@ -28,16 +28,19 @@ bun run build # bundle to dist/tracker.js (what the npm package ships)
28
28
 
29
29
  ## Configuration
30
30
 
31
- Copy `tracker.config.example.json` to `tracker.config.json` and fill in your instance:
31
+ In the project root, scaffold a config:
32
32
 
33
33
  ```sh
34
- cp tracker.config.example.json tracker.config.json
34
+ tracker init --base-url https://gitlab.example.com --project group/project
35
+ # or `tracker init` alone, then edit the placeholders
35
36
  ```
36
37
 
37
- `tracker.config.json` is **gitignored** (it carries instance/project identifiers); the
38
- committed example holds the shape. The token is also never committed (env var or gitignored
39
- `.env`). Config discovery walks up from the current directory, so any subdirectory of the
40
- project works.
38
+ `init` writes `tracker.config.json` and inside a git repository — git-ignores the
39
+ local-only files (`tracker.config.json`, `.tracker/`, `.env`) so instance and project
40
+ identifiers can never be committed. The committed `tracker.config.example.json` holds the
41
+ same shape if you prefer copying it by hand. The token is also never committed (env var or
42
+ gitignored `.env`). Config discovery walks up from the current directory, so any
43
+ subdirectory of the project works.
41
44
 
42
45
  ```json
43
46
  {
package/bin/tracker.mjs CHANGED
File without changes
package/dist/tracker.js CHANGED
@@ -2,8 +2,8 @@
2
2
  // @bun
3
3
 
4
4
  // src/cli/index.ts
5
- import { existsSync as existsSync3 } from "fs";
6
- import { resolve as resolve3 } from "path";
5
+ import { existsSync as existsSync4 } from "fs";
6
+ import { resolve as resolve4 } from "path";
7
7
 
8
8
  // src/errors.ts
9
9
  class DomainError extends Error {
@@ -758,7 +758,7 @@ function parseConfig(json, rootDir) {
758
758
  function loadConfig(startDir = process.cwd()) {
759
759
  const file = findConfigFile(startDir);
760
760
  if (!file) {
761
- throw new UsageError(`no ${CONFIG_FILENAME} found in ${startDir} or any parent directory \u2014 create one (see README) or cd into the project.`);
761
+ throw new UsageError(`no ${CONFIG_FILENAME} found in ${startDir} or any parent directory \u2014 run \`tracker init\` to create one, or cd into the project.`);
762
762
  }
763
763
  return parseConfig(readFileSync2(file, "utf8"), dirname3(file));
764
764
  }
@@ -1105,11 +1105,83 @@ async function ensureFresh(adapter, cache, staleMs, nowMs = Date.now(), onSync)
1105
1105
  return true;
1106
1106
  }
1107
1107
 
1108
+ // src/init.ts
1109
+ import { appendFileSync as appendFileSync2, existsSync as existsSync3, readFileSync as readFileSync3, writeFileSync } from "fs";
1110
+ import { dirname as dirname4, join as join3, resolve as resolve3 } from "path";
1111
+ var PLACEHOLDER_URL = "https://gitlab.example.com";
1112
+ var PLACEHOLDER_PROJECT = "group/project";
1113
+ function configTemplate(baseUrl, project) {
1114
+ const config = {
1115
+ provider: "gitlab",
1116
+ gitlab: {
1117
+ base_url: baseUrl.replace(/\/+$/, ""),
1118
+ project,
1119
+ token_env: ["TRACKER_GITLAB_TOKEN", "GITLAB_PERSONAL_ACCESS_TOKEN"],
1120
+ native_blocking: true
1121
+ },
1122
+ labels: { in_progress: "status::in-progress" },
1123
+ memory: { enabled: true, title: "\uD83D\uDCCC Project Memory", label: "meta::memory" },
1124
+ cache: { path: ".tracker/cache.sqlite", stale_minutes: 15 }
1125
+ };
1126
+ return `${JSON.stringify(config, null, 2)}
1127
+ `;
1128
+ }
1129
+ function ensureIgnored(dir, warnings) {
1130
+ const gitRoot = findGitRoot(dir);
1131
+ if (!gitRoot)
1132
+ return [];
1133
+ const entries = [
1134
+ [CONFIG_FILENAME, join3(dir, CONFIG_FILENAME)],
1135
+ [".tracker/", join3(dir, ".tracker", "cache.sqlite")],
1136
+ [".env", join3(dir, ".env")]
1137
+ ];
1138
+ const added = [];
1139
+ for (const [pattern, path] of entries) {
1140
+ const ignored = isGitIgnored(gitRoot, path);
1141
+ if (ignored === null) {
1142
+ warnings.push(`git not available \u2014 verify ${pattern} is git-ignored yourself`);
1143
+ return added;
1144
+ }
1145
+ if (ignored)
1146
+ continue;
1147
+ const gitignorePath = join3(dir, ".gitignore");
1148
+ const existing = existsSync3(gitignorePath) ? readFileSync3(gitignorePath, "utf8") : "";
1149
+ const prefix = existing && !existing.endsWith(`
1150
+ `) ? `
1151
+ ` : "";
1152
+ appendFileSync2(gitignorePath, `${prefix}${pattern}
1153
+ `);
1154
+ added.push(pattern);
1155
+ }
1156
+ return added;
1157
+ }
1158
+ function initProject(targetDir, opts) {
1159
+ const dir = resolve3(targetDir);
1160
+ const configPath = join3(dir, CONFIG_FILENAME);
1161
+ if (existsSync3(configPath)) {
1162
+ throw new UsageError(`${configPath} already exists \u2014 edit it instead of re-running init`);
1163
+ }
1164
+ const warnings = [];
1165
+ const parentConfig = findConfigFile(dirname4(dir));
1166
+ if (parentConfig) {
1167
+ warnings.push(`a ${CONFIG_FILENAME} already exists in ${dirname4(parentConfig)} \u2014 the one created here will shadow it for everything under ${dir}`);
1168
+ }
1169
+ const placeholders = !opts.baseUrl || !opts.project;
1170
+ writeFileSync(configPath, configTemplate(opts.baseUrl ?? PLACEHOLDER_URL, opts.project ?? PLACEHOLDER_PROJECT));
1171
+ const ignoreAdded = ensureIgnored(dir, warnings);
1172
+ return { configPath, placeholders, ignoreAdded, warnings };
1173
+ }
1174
+
1108
1175
  // src/cli/help.ts
1109
1176
  var HELP = `tracker \u2014 multi-backend issue tracking for humans and AI agents
1110
1177
 
1111
1178
  usage: tracker <command> [args]
1112
1179
 
1180
+ setup:
1181
+ init [--base-url <url>] [--project <group/project>]
1182
+ scaffold tracker.config.json + .gitignore entries
1183
+ doctor verify config, token, connectivity, capabilities
1184
+
1113
1185
  read commands (local cache, auto-sync when stale; all accept --json):
1114
1186
  sync refresh the local cache from the provider
1115
1187
  ready [--parent <id>] open + unblocked + unassigned + not in-progress
@@ -1121,7 +1193,6 @@ read commands (local cache, auto-sync when stale; all accept --json):
1121
1193
  users <query> resolve usernames/names to user ids
1122
1194
  whoami the authenticated user
1123
1195
  memories [filter] list project memories
1124
- doctor verify config, token, connectivity, capabilities
1125
1196
 
1126
1197
  write commands:
1127
1198
  create -t <title> [-d <desc>] [--parent <id>] [--epic <id>] [-l a,b]
@@ -1251,7 +1322,17 @@ example: tracker users mehmet`,
1251
1322
  whoami: "usage: tracker whoami [--json]",
1252
1323
  doctor: `usage: tracker doctor [--json]
1253
1324
 
1254
- Verifies config, token, REST/GraphQL connectivity, capabilities, cache.`
1325
+ Verifies config, token, REST/GraphQL connectivity, capabilities, cache.`,
1326
+ init: `usage: tracker init [--base-url <url>] [--project <group/project>]
1327
+
1328
+ Scaffolds tracker.config.json in the current directory (placeholders when the
1329
+ flags are omitted) and, inside a git repository, git-ignores the local-only
1330
+ files (tracker.config.json, .tracker/, .env) so instance and project
1331
+ identifiers can never be committed. Refuses to overwrite an existing config.
1332
+
1333
+ examples:
1334
+ tracker init --base-url https://gitlab.example.com --project group/project
1335
+ tracker init # then edit the placeholders in tracker.config.json`
1255
1336
  };
1256
1337
  function commandHelp(cmd) {
1257
1338
  return PER_COMMAND[cmd] ?? HELP;
@@ -1340,8 +1421,8 @@ function buildCtx() {
1340
1421
  token,
1341
1422
  nativeBlocking: config.gitlab.native_blocking
1342
1423
  });
1343
- const cachePath = resolve3(config.rootDir, config.cache.path);
1344
- if (!existsSync3(cachePath)) {
1424
+ const cachePath = resolve4(config.rootDir, config.cache.path);
1425
+ if (!existsSync4(cachePath)) {
1345
1426
  const guard = guardCacheIgnored(config.rootDir, cachePath);
1346
1427
  if (guard.action === "added" || guard.action === "warn")
1347
1428
  console.error(`(${guard.detail})`);
@@ -1367,7 +1448,7 @@ async function cmdSync(ctx) {
1367
1448
  const t0 = performance.now();
1368
1449
  const { count } = await syncCache(ctx.adapter, ctx.cache);
1369
1450
  const ms = Math.round(performance.now() - t0);
1370
- console.log(`synced ${count} items in ${ms}ms \u2192 ${resolve3(ctx.config.rootDir, ctx.config.cache.path)}`);
1451
+ console.log(`synced ${count} items in ${ms}ms \u2192 ${resolve4(ctx.config.rootDir, ctx.config.cache.path)}`);
1371
1452
  }
1372
1453
  async function cmdReady(ctx, args) {
1373
1454
  await freshen(ctx);
@@ -1617,7 +1698,7 @@ async function cmdDoctor(args) {
1617
1698
  name: "config",
1618
1699
  status: "fail",
1619
1700
  detail: e.message,
1620
- fix: "create tracker.config.json (see README) in the project root"
1701
+ fix: "run `tracker init` in the project root to create tracker.config.json"
1621
1702
  });
1622
1703
  }
1623
1704
  let ctx = null;
@@ -1684,7 +1765,7 @@ async function cmdDoctor(args) {
1684
1765
  status: "ok",
1685
1766
  detail: last ? `${ctx.cache.count()} items, synced ${Math.round((Date.now() - last) / 60000)}m ago` : "empty (run: tracker sync)"
1686
1767
  });
1687
- const cachePath = resolve3(config.rootDir, config.cache.path);
1768
+ const cachePath = resolve4(config.rootDir, config.cache.path);
1688
1769
  const gitRoot = findGitRoot(config.rootDir);
1689
1770
  if (gitRoot) {
1690
1771
  const ignored = isGitIgnored(gitRoot, cachePath);
@@ -1712,6 +1793,21 @@ all good.`);
1712
1793
  }
1713
1794
  return failed ? 1 : 0;
1714
1795
  }
1796
+ function cmdInit(args) {
1797
+ const result = initProject(process.cwd(), {
1798
+ baseUrl: str(args, "--base-url"),
1799
+ project: str(args, "--project")
1800
+ });
1801
+ console.log(`created ${result.configPath}`);
1802
+ for (const pattern of result.ignoreAdded)
1803
+ console.log(`added "${pattern}" to .gitignore`);
1804
+ for (const warning of result.warnings)
1805
+ console.error(`warning: ${warning}`);
1806
+ if (result.placeholders) {
1807
+ console.log("next: edit gitlab.base_url and gitlab.project in the config");
1808
+ }
1809
+ console.log("then: export TRACKER_GITLAB_TOKEN (or add it to .env) and run: tracker doctor");
1810
+ }
1715
1811
  var VALUE_FLAGS = {
1716
1812
  ready: { "--parent": "value", "--json": "bool" },
1717
1813
  show: { "--json": "bool" },
@@ -1741,6 +1837,7 @@ var VALUE_FLAGS = {
1741
1837
  users: { "--json": "bool" },
1742
1838
  whoami: { "--json": "bool" },
1743
1839
  doctor: { "--json": "bool" },
1840
+ init: { "--base-url": "value", "--project": "value" },
1744
1841
  memories: { "--json": "bool" },
1745
1842
  comment: {},
1746
1843
  comments: { "--json": "bool" },
@@ -1781,6 +1878,10 @@ ${HELP}`);
1781
1878
  return 0;
1782
1879
  }
1783
1880
  const args = parseArgs(rest, spec, ALIASES[cmd] ?? {});
1881
+ if (cmd === "init") {
1882
+ cmdInit(args);
1883
+ return 0;
1884
+ }
1784
1885
  if (cmd === "doctor")
1785
1886
  return cmdDoctor(args);
1786
1887
  const ctx = buildCtx();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "trackerctl",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Multi-backend issue-tracking CLI for humans and AI agents — claim races, dependencies, hierarchy, local FTS search, project memory. GitLab adapter first.",
5
5
  "license": "MIT",
6
6
  "author": "refo",