@tmcayden/youtrack-axi 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.
@@ -0,0 +1,101 @@
1
+ import { AxiError, installSessionStartHooks, sessionStartHookStatus } from "axi-sdk-js";
2
+ import { collapseHome } from "../render.js";
3
+ import { boolFlag, parseArgs, rejectExtraPositionals, requireString } from "../flags.js";
4
+ import { resolveConfig, writeDedicatedConfig } from "../config.js";
5
+ import { YouTrackClient } from "../youtrack.js";
6
+ const HOOK_MARKER = "youtrack-axi";
7
+ export const setupSpec = {
8
+ name: "setup",
9
+ usage: 'setup [--url "https://<host>" --token "<permanent-token>"] [--hooks]',
10
+ description: "Store YouTrack credentials and optionally register the session-start home view",
11
+ flags: {
12
+ "--help": { kind: "boolean", description: "Show this help" },
13
+ "--url": { kind: "string", description: "YouTrack base URL" },
14
+ "--token": { kind: "string", description: "YouTrack permanent token" },
15
+ "--hooks": { kind: "boolean", description: "Register a SessionStart hook for Claude Code, Codex, and OpenCode" },
16
+ "--verify": { kind: "boolean", description: "Check the stored credentials against the instance" },
17
+ },
18
+ examples: [
19
+ 'youtrack-axi setup --url "https://example.youtrack.cloud" --token "perm:..."',
20
+ "youtrack-axi setup --verify",
21
+ "youtrack-axi setup --hooks",
22
+ ],
23
+ };
24
+ export async function runSetup(args) {
25
+ const parsed = parseArgs(setupSpec, args);
26
+ rejectExtraPositionals(parsed, setupSpec, 0);
27
+ const wantsHooks = boolFlag(parsed, "--hooks");
28
+ const wantsVerify = boolFlag(parsed, "--verify");
29
+ const hasUrl = parsed.flags["--url"] !== undefined;
30
+ const hasToken = parsed.flags["--token"] !== undefined;
31
+ if (!hasUrl && !hasToken && !wantsHooks && !wantsVerify) {
32
+ throw new AxiError("setup needs something to do", "VALIDATION_ERROR", [
33
+ `Run \`youtrack-axi ${setupSpec.usage}\``,
34
+ "Run `youtrack-axi setup --verify` to test credentials that are already configured",
35
+ ]);
36
+ }
37
+ const output = {};
38
+ if (hasUrl || hasToken) {
39
+ const url = requireString(parsed, setupSpec, "--url");
40
+ const token = requireString(parsed, setupSpec, "--token");
41
+ const result = writeDedicatedConfig(url, token);
42
+ output["config"] = result.skipped
43
+ ? {
44
+ written: "no",
45
+ reason: result.reason ?? "",
46
+ existing: collapseHome(result.path),
47
+ }
48
+ : { written: "yes", path: collapseHome(result.path), mode: "0600" };
49
+ }
50
+ if (wantsHooks) {
51
+ output["hooks"] = installHooks();
52
+ }
53
+ if (wantsVerify || hasUrl || hasToken) {
54
+ output["verify"] = await verify();
55
+ }
56
+ return {
57
+ ...output,
58
+ help: [
59
+ "Run `youtrack-axi` to see your open issues",
60
+ ...(wantsHooks ? [] : ["Run `youtrack-axi setup --hooks` to show that home view at every session start"]),
61
+ ],
62
+ };
63
+ }
64
+ function installHooks() {
65
+ const messages = [];
66
+ installSessionStartHooks({
67
+ marker: HOOK_MARKER,
68
+ binaryNames: ["youtrack-axi"],
69
+ onError: (message) => messages.push(message),
70
+ });
71
+ const status = sessionStartHookStatus({ marker: HOOK_MARKER });
72
+ const agents = [
73
+ { agent: "claude", ...describe(status.claude) },
74
+ { agent: "codex", ...describe(status.codex) },
75
+ { agent: "opencode", ...describe(status.opencode) },
76
+ ];
77
+ const warnings = [...messages];
78
+ if (status.codex.installed && !status.codex.userFeatureEnabled) {
79
+ warnings.push(`Set [features].hooks = true in ${collapseHome(status.codex.userFeaturePath)} for Codex to run the hook`);
80
+ }
81
+ return {
82
+ agents,
83
+ ...(warnings.length > 0 ? { warnings } : {}),
84
+ };
85
+ }
86
+ function describe(state) {
87
+ return { installed: state.installed ? "yes" : "no", path: collapseHome(state.path) };
88
+ }
89
+ /** Confirms the credentials work without ever printing the token itself. */
90
+ async function verify() {
91
+ const config = resolveConfig();
92
+ const client = new YouTrackClient(config);
93
+ const me = await client.get("users/me", { fields: "login,name" });
94
+ return {
95
+ url: config.url,
96
+ source: collapseHome(config.source),
97
+ authenticated_as: me.login ?? "",
98
+ token: "stored, not shown",
99
+ };
100
+ }
101
+ //# sourceMappingURL=setup.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"setup.js","sourceRoot":"","sources":["../../src/commands/setup.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ import type { StructuredOutput } from "../render.js";
2
+ import { type CommandSpec } from "../flags.js";
3
+ import { YouTrackClient } from "../youtrack.js";
4
+ export declare const workSpec: CommandSpec;
5
+ export declare function runWork(args: string[], client: YouTrackClient): Promise<StructuredOutput>;
@@ -0,0 +1,145 @@
1
+ import { AxiError } from "axi-sdk-js";
2
+ import { optionalString, parseArgs, positiveInt, rejectExtraPositionals, requirePositional, } from "../flags.js";
3
+ import { formatDate, formatMinutes, parseDuration, parseWorkDate, totalMinutes, workRow } from "../format.js";
4
+ import { WORK_ITEM_FIELDS } from "../youtrack.js";
5
+ const DEFAULT_LIMIT = 50;
6
+ export const workSpec = {
7
+ name: "work",
8
+ usage: 'work <ID> [--log <duration> [--date YYYY-MM-DD] [--text "<note>"] [--type <name>]] [--limit <n>]',
9
+ description: "List logged work on an issue, or log new work against it",
10
+ flags: {
11
+ "--help": { kind: "boolean", description: "Show this help" },
12
+ "--log": { kind: "string", description: "Duration to log, e.g. 30m, 1h, 1h 30m, 2d, or bare minutes" },
13
+ "--date": { kind: "string", description: "Work date as YYYY-MM-DD", default: "today" },
14
+ "--text": { kind: "string", description: "Note describing the work" },
15
+ "--type": { kind: "string", description: "Work item type name, e.g. Development" },
16
+ "--limit": { kind: "string", description: "Maximum work items to list", default: String(DEFAULT_LIMIT) },
17
+ },
18
+ examples: [
19
+ "youtrack-axi work GSE-123",
20
+ 'youtrack-axi work GSE-123 --log 1h30m --text "Traced the zoom regression"',
21
+ "youtrack-axi work GSE-123 --log 2h --date 2026-09-05 --type Development",
22
+ ],
23
+ };
24
+ export async function runWork(args, client) {
25
+ const parsed = parseArgs(workSpec, args);
26
+ const id = requirePositional(parsed, workSpec, 0, "<ID>");
27
+ rejectExtraPositionals(parsed, workSpec, 1);
28
+ const log = optionalString(parsed, "--log");
29
+ if (log === undefined) {
30
+ for (const dependent of ["--date", "--text", "--type"]) {
31
+ if (optionalString(parsed, dependent) !== undefined) {
32
+ throw new AxiError(`${dependent} only applies when logging work`, "VALIDATION_ERROR", [
33
+ `Add --log <duration>, e.g. \`youtrack-axi work ${id} --log 1h ${dependent} ...\``,
34
+ `Run \`youtrack-axi work ${id}\` to list existing work items`,
35
+ ]);
36
+ }
37
+ }
38
+ return listWork(client, id, positiveInt(parsed, workSpec, "--limit", DEFAULT_LIMIT));
39
+ }
40
+ return logWork(client, id, parsed);
41
+ }
42
+ /** YouTrack rejects a work item type given by name alone, so the name is resolved to its id. */
43
+ async function resolveWorkItemType(client, name) {
44
+ if (name === undefined)
45
+ return undefined;
46
+ const types = await client.get("admin/timeTrackingSettings/workItemTypes", { fields: "id,name", $top: 200 });
47
+ const match = types.find((t) => t.name?.toLowerCase() === name.toLowerCase());
48
+ if (!match?.id) {
49
+ throw new AxiError(`No work item type named "${name}"`, "VALIDATION_ERROR", [
50
+ `Valid types: ${types.map((t) => t.name).filter(Boolean).join(", ")}`,
51
+ "Omit --type to use the project default",
52
+ ]);
53
+ }
54
+ return { id: match.id };
55
+ }
56
+ /**
57
+ * YouTrack answers a POST to a project with time tracking switched off with a
58
+ * bare 403, which reads as a token problem and sends an agent down the wrong
59
+ * path. The real cause is only visible in the project settings, so it is looked
60
+ * up here - on the failure path only, never on the happy path.
61
+ */
62
+ async function explainLogFailure(client, id, error) {
63
+ if (!(error instanceof AxiError) || error.code !== "AUTH_ERROR")
64
+ return error;
65
+ const shortName = id.includes("-") ? id.slice(0, id.lastIndexOf("-")) : id;
66
+ try {
67
+ const projects = await client.get("admin/projects", {
68
+ fields: "id,shortName",
69
+ $top: 500,
70
+ });
71
+ const project = projects.find((p) => p.shortName?.toLowerCase() === shortName.toLowerCase());
72
+ if (!project?.id)
73
+ return error;
74
+ const settings = await client.get(`admin/projects/${project.id}/timeTrackingSettings`, { fields: "enabled" });
75
+ if (settings.enabled === false) {
76
+ return new AxiError(`Time tracking is not enabled for project ${shortName}`, "API_ERROR", [
77
+ `Ask a YouTrack administrator to enable time tracking for ${shortName}`,
78
+ `Run \`youtrack-axi work ${id}\` to read existing work items, which stays available either way`,
79
+ ]);
80
+ }
81
+ return new AxiError(`This account cannot log work in project ${shortName}`, "AUTH_ERROR", [
82
+ `Ask a YouTrack administrator for the create-work-item permission on ${shortName}`,
83
+ `Run \`youtrack-axi work ${id}\` to read existing work items, which stays available either way`,
84
+ ]);
85
+ }
86
+ catch {
87
+ /* diagnostics are best effort - fall back to the original error */
88
+ }
89
+ return error;
90
+ }
91
+ async function listWork(client, id, limit) {
92
+ const items = await client.get(`issues/${encodeURIComponent(id)}/timeTracking/workItems`, {
93
+ fields: WORK_ITEM_FIELDS,
94
+ $top: limit,
95
+ });
96
+ if (items.length === 0) {
97
+ return {
98
+ work: `0 work items logged on ${id}`,
99
+ help: [`Run \`youtrack-axi work ${id} --log <duration>\` to log time against it`],
100
+ };
101
+ }
102
+ const sorted = [...items].sort((a, b) => (b.date ?? 0) - (a.date ?? 0));
103
+ return {
104
+ issue: id,
105
+ total: formatMinutes(totalMinutes(items)),
106
+ count: items.length,
107
+ work: sorted.map(workRow),
108
+ help: [`Run \`youtrack-axi work ${id} --log <duration> --text "<note>"\` to add another entry`],
109
+ };
110
+ }
111
+ async function logWork(client, id, parsed) {
112
+ const duration = parseDuration(optionalString(parsed, "--log"), workSpec.usage);
113
+ const date = parseWorkDate(optionalString(parsed, "--date"), workSpec.usage);
114
+ const text = optionalString(parsed, "--text");
115
+ const type = await resolveWorkItemType(client, optionalString(parsed, "--type"));
116
+ let created;
117
+ try {
118
+ created = await client.post(`issues/${encodeURIComponent(id)}/timeTracking/workItems`, {
119
+ duration,
120
+ date,
121
+ ...(text ? { text } : {}),
122
+ ...(type ? { type } : {}),
123
+ }, { fields: WORK_ITEM_FIELDS });
124
+ }
125
+ catch (error) {
126
+ throw await explainLogFailure(client, id, error);
127
+ }
128
+ const existing = await client.get(`issues/${encodeURIComponent(id)}/timeTracking/workItems`, {
129
+ fields: "duration(minutes)",
130
+ $top: 500,
131
+ });
132
+ return {
133
+ logged: {
134
+ issue: id,
135
+ date: created.date ? formatDate(created.date) : formatDate(date),
136
+ duration: created.duration?.presentation ?? formatMinutes(created.duration?.minutes ?? 0),
137
+ type: created.type?.name ?? "",
138
+ author: created.author?.login ?? "",
139
+ ...(text ? { text } : {}),
140
+ },
141
+ issue_total: formatMinutes(totalMinutes(existing)),
142
+ help: [`Run \`youtrack-axi work ${id}\` to list every entry on this issue`],
143
+ };
144
+ }
145
+ //# sourceMappingURL=work.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"work.js","sourceRoot":"","sources":["../../src/commands/work.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ export interface Config {
2
+ url: string;
3
+ token: string;
4
+ source: string;
5
+ }
6
+ export declare function sharedConfigPath(): string | undefined;
7
+ export declare function dedicatedConfigPath(): string;
8
+ export declare function resolveConfig(): Config;
9
+ export interface WriteConfigResult {
10
+ path: string;
11
+ skipped: boolean;
12
+ reason?: string;
13
+ }
14
+ export declare function writeDedicatedConfig(url: string, token: string): WriteConfigResult;
package/dist/config.js ADDED
@@ -0,0 +1,59 @@
1
+ import { readFileSync, writeFileSync, mkdirSync, existsSync } from "node:fs";
2
+ import { homedir } from "node:os";
3
+ import { dirname, join } from "node:path";
4
+ import { AxiError } from "axi-sdk-js";
5
+ export function sharedConfigPath() {
6
+ const root = process.env.HARNESS_STATE_ROOT;
7
+ return root ? join(root, ".timesheet.json") : undefined;
8
+ }
9
+ export function dedicatedConfigPath() {
10
+ return join(process.env.XDG_CONFIG_HOME ?? join(homedir(), ".config"), "youtrack-axi", "config.json");
11
+ }
12
+ function readConfigFile(path) {
13
+ if (!path || !existsSync(path))
14
+ return undefined;
15
+ try {
16
+ const parsed = JSON.parse(readFileSync(path, "utf8"));
17
+ return typeof parsed === "object" && parsed !== null ? parsed : undefined;
18
+ }
19
+ catch {
20
+ throw new AxiError(`Config file at ${path} is not valid JSON`, "CONFIG_ERROR", [
21
+ `Repair ${path}, or run \`youtrack-axi setup --url "https://<host>" --token "<token>"\``,
22
+ ]);
23
+ }
24
+ }
25
+ export function resolveConfig() {
26
+ const shared = sharedConfigPath();
27
+ const candidates = [
28
+ { file: readConfigFile(shared), source: shared ?? "shared config" },
29
+ { file: readConfigFile(dedicatedConfigPath()), source: dedicatedConfigPath() },
30
+ ];
31
+ const found = candidates.find((c) => c.file?.youtrack_url && c.file?.youtrack_token);
32
+ const url = process.env.YOUTRACK_URL ?? found?.file?.youtrack_url;
33
+ const token = process.env.YOUTRACK_TOKEN ?? found?.file?.youtrack_token;
34
+ if (!url || !token) {
35
+ const missing = [!url ? "URL" : undefined, !token ? "token" : undefined].filter(Boolean).join(" and ");
36
+ throw new AxiError(`YouTrack ${missing} not configured`, "CONFIG_ERROR", [
37
+ `Run \`youtrack-axi setup --url "https://example.youtrack.cloud" --token "<permanent-token>"\``,
38
+ "Or export YOUTRACK_URL and YOUTRACK_TOKEN",
39
+ ]);
40
+ }
41
+ const source = process.env.YOUTRACK_URL || process.env.YOUTRACK_TOKEN ? "environment" : (found?.source ?? "environment");
42
+ return { url: url.replace(/\/+$/, ""), token, source };
43
+ }
44
+ export function writeDedicatedConfig(url, token) {
45
+ const shared = readConfigFile(sharedConfigPath());
46
+ if (shared?.youtrack_url && shared?.youtrack_token) {
47
+ return {
48
+ path: sharedConfigPath(),
49
+ skipped: true,
50
+ reason: "shared config already provides credentials",
51
+ };
52
+ }
53
+ const path = dedicatedConfigPath();
54
+ mkdirSync(dirname(path), { recursive: true });
55
+ const existing = readConfigFile(path) ?? {};
56
+ writeFileSync(path, `${JSON.stringify({ ...existing, youtrack_url: url.replace(/\/+$/, ""), youtrack_token: token }, null, 2)}\n`, { mode: 0o600 });
57
+ return { path, skipped: false };
58
+ }
59
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAC7E,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAatC,MAAM,UAAU,gBAAgB;IAC9B,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC;IAC5C,OAAO,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,EAAE,iBAAiB,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;AAC1D,CAAC;AAED,MAAM,UAAU,mBAAmB;IACjC,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,eAAe,IAAI,IAAI,CAAC,OAAO,EAAE,EAAE,SAAS,CAAC,EAAE,cAAc,EAAE,aAAa,CAAC,CAAC;AACxG,CAAC;AAED,SAAS,cAAc,CAAC,IAAwB;IAC9C,IAAI,CAAC,IAAI,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,SAAS,CAAC;IACjD,IAAI,CAAC;QACH,MAAM,MAAM,GAAY,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC;QAC/D,OAAO,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,IAAI,CAAC,CAAC,CAAE,MAAqB,CAAC,CAAC,CAAC,SAAS,CAAC;IAC5F,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,QAAQ,CAAC,kBAAkB,IAAI,oBAAoB,EAAE,cAAc,EAAE;YAC7E,UAAU,IAAI,0EAA0E;SACzF,CAAC,CAAC;IACL,CAAC;AACH,CAAC;AAED,MAAM,UAAU,aAAa;IAC3B,MAAM,MAAM,GAAG,gBAAgB,EAAE,CAAC;IAClC,MAAM,UAAU,GAA4D;QAC1E,EAAE,IAAI,EAAE,cAAc,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,MAAM,IAAI,eAAe,EAAE;QACnE,EAAE,IAAI,EAAE,cAAc,CAAC,mBAAmB,EAAE,CAAC,EAAE,MAAM,EAAE,mBAAmB,EAAE,EAAE;KAC/E,CAAC;IACF,MAAM,KAAK,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,YAAY,IAAI,CAAC,CAAC,IAAI,EAAE,cAAc,CAAC,CAAC;IAErF,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,YAAY,IAAI,KAAK,EAAE,IAAI,EAAE,YAAY,CAAC;IAClE,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,cAAc,IAAI,KAAK,EAAE,IAAI,EAAE,cAAc,CAAC;IAExE,IAAI,CAAC,GAAG,IAAI,CAAC,KAAK,EAAE,CAAC;QACnB,MAAM,OAAO,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACvG,MAAM,IAAI,QAAQ,CAAC,YAAY,OAAO,iBAAiB,EAAE,cAAc,EAAE;YACvE,+FAA+F;YAC/F,2CAA2C;SAC5C,CAAC,CAAC;IACL,CAAC;IAED,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,YAAY,IAAI,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,KAAK,EAAE,MAAM,IAAI,aAAa,CAAC,CAAC;IACzH,OAAO,EAAE,GAAG,EAAE,GAAG,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC;AACzD,CAAC;AAQD,MAAM,UAAU,oBAAoB,CAAC,GAAW,EAAE,KAAa;IAC7D,MAAM,MAAM,GAAG,cAAc,CAAC,gBAAgB,EAAE,CAAC,CAAC;IAClD,IAAI,MAAM,EAAE,YAAY,IAAI,MAAM,EAAE,cAAc,EAAE,CAAC;QACnD,OAAO;YACL,IAAI,EAAE,gBAAgB,EAAY;YAClC,OAAO,EAAE,IAAI;YACb,MAAM,EAAE,4CAA4C;SACrD,CAAC;IACJ,CAAC;IACD,MAAM,IAAI,GAAG,mBAAmB,EAAE,CAAC;IACnC,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC9C,MAAM,QAAQ,GAAG,cAAc,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;IAC5C,aAAa,CACX,IAAI,EACJ,GAAG,IAAI,CAAC,SAAS,CAAC,EAAE,GAAG,QAAQ,EAAE,YAAY,EAAE,GAAG,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,EAAE,cAAc,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAC7G,EAAE,IAAI,EAAE,KAAK,EAAE,CAChB,CAAC;IACF,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;AAClC,CAAC"}
@@ -0,0 +1,25 @@
1
+ export type FlagKind = "string" | "boolean";
2
+ export interface FlagSpec {
3
+ kind: FlagKind;
4
+ description: string;
5
+ default?: string;
6
+ }
7
+ export interface CommandSpec {
8
+ name: string;
9
+ usage: string;
10
+ description: string;
11
+ flags: Record<string, FlagSpec>;
12
+ examples: string[];
13
+ }
14
+ export interface ParsedArgs {
15
+ positionals: string[];
16
+ flags: Record<string, string | true>;
17
+ }
18
+ export declare function parseArgs(spec: CommandSpec, args: string[]): ParsedArgs;
19
+ export declare function requireString(parsed: ParsedArgs, spec: CommandSpec, name: string): string;
20
+ export declare function optionalString(parsed: ParsedArgs, name: string): string | undefined;
21
+ export declare function boolFlag(parsed: ParsedArgs, name: string): boolean;
22
+ export declare function positiveInt(parsed: ParsedArgs, spec: CommandSpec, name: string, fallback: number): number;
23
+ export declare function requirePositional(parsed: ParsedArgs, spec: CommandSpec, index: number, label: string): string;
24
+ export declare function rejectExtraPositionals(parsed: ParsedArgs, spec: CommandSpec, allowed: number): void;
25
+ export declare function renderCommandHelp(spec: CommandSpec): Record<string, unknown>;
package/dist/flags.js ADDED
@@ -0,0 +1,123 @@
1
+ import { AxiError } from "axi-sdk-js";
2
+ /** Flags an agent is likely to guess from other trackers, pointed at the real name. */
3
+ const RENAMED_FLAGS = {
4
+ "--title": "--summary",
5
+ "--body": "--description",
6
+ "--status": "--state",
7
+ "--top": "--limit",
8
+ "--count": "--limit",
9
+ "--message": "--text",
10
+ "--comment": "--text",
11
+ "--duration": "--log",
12
+ "--time": "--log",
13
+ "--field": "--fields",
14
+ };
15
+ export function parseArgs(spec, args) {
16
+ const positionals = [];
17
+ const flags = {};
18
+ for (let i = 0; i < args.length; i++) {
19
+ const arg = args[i];
20
+ if (!arg.startsWith("--") || arg === "--") {
21
+ positionals.push(arg);
22
+ continue;
23
+ }
24
+ const eq = arg.indexOf("=");
25
+ const name = eq === -1 ? arg : arg.slice(0, eq);
26
+ const inlineValue = eq === -1 ? undefined : arg.slice(eq + 1);
27
+ const flag = spec.flags[name];
28
+ if (!flag) {
29
+ throw unknownFlag(spec, name);
30
+ }
31
+ if (flag.kind === "boolean") {
32
+ if (inlineValue !== undefined) {
33
+ throw new AxiError(`Flag ${name} for \`${spec.name}\` does not take a value`, "VALIDATION_ERROR", [
34
+ `Run \`youtrack-axi ${spec.usage}\``,
35
+ ]);
36
+ }
37
+ flags[name] = true;
38
+ continue;
39
+ }
40
+ const value = inlineValue ?? args[++i];
41
+ if (value === undefined) {
42
+ throw new AxiError(`Flag ${name} for \`${spec.name}\` requires a value`, "VALIDATION_ERROR", [
43
+ `Run \`youtrack-axi ${spec.usage}\``,
44
+ ]);
45
+ }
46
+ flags[name] = value;
47
+ }
48
+ return { positionals, flags };
49
+ }
50
+ function unknownFlag(spec, name) {
51
+ const renamed = RENAMED_FLAGS[name];
52
+ const valid = Object.keys(spec.flags).filter((name) => name !== "--help").join(", ");
53
+ const suggestions = renamed && spec.flags[renamed]
54
+ ? [`${name} is not a flag here; use ${renamed} instead`, `Run \`youtrack-axi ${spec.usage}\``]
55
+ : [`valid flags for \`${spec.name}\`: ${valid} (--help always allowed)`, `Run \`youtrack-axi ${spec.usage}\``];
56
+ return new AxiError(`Unknown flag ${name} for \`${spec.name}\``, "VALIDATION_ERROR", suggestions);
57
+ }
58
+ export function requireString(parsed, spec, name) {
59
+ const value = parsed.flags[name];
60
+ if (typeof value !== "string" || value.length === 0) {
61
+ throw new AxiError(`${name} is required for \`${spec.name}\``, "VALIDATION_ERROR", [
62
+ `Run \`youtrack-axi ${spec.usage}\``,
63
+ ...spec.examples.slice(0, 1).map((e) => `Example: ${e}`),
64
+ ]);
65
+ }
66
+ return value;
67
+ }
68
+ export function optionalString(parsed, name) {
69
+ const value = parsed.flags[name];
70
+ return typeof value === "string" ? value : undefined;
71
+ }
72
+ export function boolFlag(parsed, name) {
73
+ return parsed.flags[name] === true;
74
+ }
75
+ export function positiveInt(parsed, spec, name, fallback) {
76
+ const raw = optionalString(parsed, name);
77
+ if (raw === undefined)
78
+ return fallback;
79
+ const value = Number(raw);
80
+ if (!Number.isInteger(value) || value < 1) {
81
+ throw new AxiError(`${name} must be a positive integer, got "${raw}"`, "VALIDATION_ERROR", [
82
+ `Run \`youtrack-axi ${spec.usage}\``,
83
+ ]);
84
+ }
85
+ return value;
86
+ }
87
+ export function requirePositional(parsed, spec, index, label) {
88
+ const value = parsed.positionals[index];
89
+ if (value === undefined || value.length === 0) {
90
+ throw new AxiError(`${label} is required for \`${spec.name}\``, "VALIDATION_ERROR", [
91
+ `Run \`youtrack-axi ${spec.usage}\``,
92
+ ...spec.examples.slice(0, 1).map((e) => `Example: ${e}`),
93
+ ]);
94
+ }
95
+ return value;
96
+ }
97
+ export function rejectExtraPositionals(parsed, spec, allowed) {
98
+ if (parsed.positionals.length > allowed) {
99
+ const extra = parsed.positionals.slice(allowed).join(" ");
100
+ throw new AxiError(`Unexpected argument "${extra}" for \`${spec.name}\``, "VALIDATION_ERROR", [
101
+ `Run \`youtrack-axi ${spec.usage}\``,
102
+ allowed === 0
103
+ ? `\`${spec.name}\` takes no positional arguments`
104
+ : `Quote multi-word values: youtrack-axi ${spec.usage}`,
105
+ ]);
106
+ }
107
+ }
108
+ export function renderCommandHelp(spec) {
109
+ const flags = {};
110
+ for (const [name, flag] of Object.entries(spec.flags)) {
111
+ if (name === "--help")
112
+ continue;
113
+ flags[name] = flag.default ? `${flag.description} (default: ${flag.default})` : flag.description;
114
+ }
115
+ return {
116
+ command: spec.name,
117
+ description: spec.description,
118
+ usage: `youtrack-axi ${spec.usage}`,
119
+ ...(Object.keys(flags).length > 0 ? { flags } : {}),
120
+ examples: spec.examples,
121
+ };
122
+ }
123
+ //# sourceMappingURL=flags.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"flags.js","sourceRoot":"","sources":["../src/flags.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,41 @@
1
+ import type { CustomField, Issue, WorkItem } from "./youtrack.js";
2
+ export declare const DESCRIPTION_PREVIEW_CHARS = 800;
3
+ /**
4
+ * YouTrack returns every custom field as {name, value:{name|login|...}}. Agents
5
+ * pay for that nesting twice - once in tokens, once in traversal - so it is
6
+ * flattened to plain scalars here and never reaches stdout in nested form.
7
+ */
8
+ export declare function flattenCustomFields(fields: CustomField[] | undefined): Record<string, string>;
9
+ export interface IssueRow {
10
+ id: string;
11
+ summary: string;
12
+ state: string;
13
+ }
14
+ export declare function issueRow(issue: Issue): IssueRow;
15
+ export declare function issueRowWithFields(issue: Issue, extraFields: string[]): Record<string, string>;
16
+ export interface TruncatedText {
17
+ text: string;
18
+ truncated: boolean;
19
+ totalChars: number;
20
+ }
21
+ export declare function truncate(value: string | null | undefined, limit?: number): TruncatedText;
22
+ export declare function formatDate(epochMs: number): string;
23
+ export declare function formatMinutes(minutes: number): string;
24
+ export interface WorkRow {
25
+ date: string;
26
+ duration: string;
27
+ author: string;
28
+ type: string;
29
+ text: string;
30
+ }
31
+ export declare function workRow(item: WorkItem): WorkRow;
32
+ export declare function totalMinutes(items: WorkItem[]): number;
33
+ /** YouTrack parses `presentation` itself; a bare number is taken as minutes. */
34
+ export declare function parseDuration(raw: string, usage: string): {
35
+ presentation: string;
36
+ } | {
37
+ minutes: number;
38
+ };
39
+ /** Anchored to local noon so a date never slips a day when converted to UTC. */
40
+ export declare function parseWorkDate(raw: string | undefined, usage: string): number;
41
+ export declare function splitFieldList(raw: string | undefined): string[];