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 +9 -6
- package/bin/tracker.mjs +0 -0
- package/dist/tracker.js +111 -10
- package/package.json +1 -1
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
|
-
|
|
31
|
+
In the project root, scaffold a config:
|
|
32
32
|
|
|
33
33
|
```sh
|
|
34
|
-
|
|
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`
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
|
6
|
-
import { resolve as
|
|
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
|
|
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 =
|
|
1344
|
-
if (!
|
|
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 ${
|
|
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: "
|
|
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 =
|
|
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.
|
|
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",
|