opctl 0.1.2 → 0.1.4
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 +35 -9
- package/dist/cli.js +488 -58
- package/dist/cli.js.map +1 -1
- package/package.json +6 -2
package/README.md
CHANGED
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
|
|
7
7
|
Published package:
|
|
8
8
|
|
|
9
|
-
-
|
|
10
|
-
- current package: `opctl@0.1.
|
|
9
|
+
- npmjs.com: `opctl`
|
|
10
|
+
- current package: `opctl@0.1.4`
|
|
11
11
|
- binary: `opctl`
|
|
12
12
|
|
|
13
13
|
Install globally:
|
|
@@ -25,8 +25,7 @@ npx opctl --help
|
|
|
25
25
|
|
|
26
26
|
## Configuration
|
|
27
27
|
|
|
28
|
-
Export variables in your shell
|
|
29
|
-
|
|
28
|
+
Export variables in your shell, load a local file with `--env <path>`, or save non-write defaults in a profile. By default, `opctl` also reads `.env` from the current working directory when present; pass `--no-env` to disable that.
|
|
30
29
|
Required:
|
|
31
30
|
|
|
32
31
|
- `OPENPROJECT_URL`: OpenProject instance URL, optionally including an instance path prefix.
|
|
@@ -38,6 +37,18 @@ Optional:
|
|
|
38
37
|
- `OPENPROJECT_DEFAULT_PROJECT`: project identifier/id used by `wp search` when `--project` is omitted.
|
|
39
38
|
- `OPENPROJECT_ALLOW_WRITE`: must be exactly `1` to allow write-capable commands.
|
|
40
39
|
|
|
40
|
+
Profile commands:
|
|
41
|
+
|
|
42
|
+
```sh
|
|
43
|
+
opctl profile set navlin-qa --url https://openproject.example.com --auth-mode bearer --default-project qa --token ...
|
|
44
|
+
opctl profile use navlin-qa
|
|
45
|
+
opctl --profile navlin-qa me --json
|
|
46
|
+
opctl profile show navlin-qa
|
|
47
|
+
opctl profile list
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Profiles are stored under `${XDG_CONFIG_HOME:-~/.config}/opctl/profiles.json`. The file is written with restrictive permissions where supported, tokens may be stored there, and profile display commands redact tokens. `OPENPROJECT_ALLOW_WRITE` is never loaded from `.env` files or profiles; writes still require the real process environment variable.
|
|
51
|
+
|
|
41
52
|
## Usage
|
|
42
53
|
|
|
43
54
|
Show the authenticated OpenProject user:
|
|
@@ -67,14 +78,21 @@ Read work packages:
|
|
|
67
78
|
opctl wp get 123
|
|
68
79
|
opctl wp get 123 --json
|
|
69
80
|
opctl wp get 123 --raw-json
|
|
81
|
+
opctl wp get 123 124 --table
|
|
82
|
+
opctl wp get --ids 123,124 --fields id,subject,status,assignee --table
|
|
83
|
+
opctl wp get --ids 123,124 --jsonl
|
|
70
84
|
```
|
|
71
85
|
|
|
86
|
+
Field selection supports `id,subject,status,type,assignee,project,href,updatedAt,description,shortDescription,attachmentsCount,lockVersion`; aliases: `title=subject`, `url=href`.
|
|
87
|
+
|
|
72
88
|
Search work packages:
|
|
73
89
|
|
|
74
90
|
```sh
|
|
75
91
|
opctl wp search --project my-project --subject "pump"
|
|
76
92
|
opctl wp search --project my-project --assignee-me --status open
|
|
77
|
-
opctl wp search --subject "pump" --
|
|
93
|
+
opctl wp search --open --subject "pump" --compact
|
|
94
|
+
opctl wp search --subject "pump" --fields id,subject,status --json
|
|
95
|
+
|
|
78
96
|
```
|
|
79
97
|
|
|
80
98
|
If `--project` is omitted, `opctl wp search` uses `OPENPROJECT_DEFAULT_PROJECT` when set. Without either, it searches the instance-wide work package endpoint.
|
|
@@ -83,7 +101,15 @@ List work packages assigned to the authenticated user:
|
|
|
83
101
|
|
|
84
102
|
```sh
|
|
85
103
|
opctl wp mine
|
|
86
|
-
opctl wp mine --
|
|
104
|
+
opctl wp mine --open --table
|
|
105
|
+
opctl wp mine --project my-project --page-size 50 --fields id,subject,status,updatedAt
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Triage a known list:
|
|
109
|
+
|
|
110
|
+
```sh
|
|
111
|
+
opctl wp check 123 124
|
|
112
|
+
opctl wp check --ids 123,124 --fields id,title,status,assignee,shortDescription,attachmentsCount --table
|
|
87
113
|
```
|
|
88
114
|
|
|
89
115
|
Pull the OpenAPI spec from your configured instance:
|
|
@@ -95,7 +121,7 @@ opctl spec pull
|
|
|
95
121
|
Write-capable command:
|
|
96
122
|
|
|
97
123
|
```sh
|
|
98
|
-
OPENPROJECT_ALLOW_WRITE=1 opctl wp comment 123
|
|
124
|
+
OPENPROJECT_ALLOW_WRITE=1 opctl wp comment 123 --dry-run "Investigating"
|
|
99
125
|
OPENPROJECT_ALLOW_WRITE=1 opctl wp comment 123 "Investigating"
|
|
100
126
|
```
|
|
101
127
|
|
|
@@ -124,7 +150,7 @@ node dist/cli.js wp --help
|
|
|
124
150
|
## Safety model
|
|
125
151
|
|
|
126
152
|
- No token or `Authorization` header is printed by normal errors, JSON output, spec pulling, or tests.
|
|
127
|
-
- `.env` files are
|
|
128
|
-
- OpenProject writes are blocked unless `OPENPROJECT_ALLOW_WRITE=1` exactly.
|
|
153
|
+
- Local `.env` files are loaded for read configuration by default; `--no-env` disables that, and `.env` cannot enable writes.
|
|
154
|
+
- OpenProject writes are blocked unless the real process environment contains `OPENPROJECT_ALLOW_WRITE=1` exactly.
|
|
129
155
|
- Every write-capable command supports `--dry-run` and avoids mutation in dry-run mode.
|
|
130
156
|
- No destructive commands are implemented: no delete, close, archive, move, or bulk edit.
|
package/dist/cli.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { realpathSync } from "node:fs";
|
|
2
|
+
import { chmodSync, existsSync, mkdirSync, readFileSync, realpathSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
4
|
import { Command } from "commander";
|
|
5
5
|
import createClient from "openapi-fetch";
|
|
6
|
+
import { dirname, join, resolve } from "node:path";
|
|
7
|
+
import { homedir } from "node:os";
|
|
6
8
|
import { mkdir, writeFile } from "node:fs/promises";
|
|
7
|
-
import { dirname } from "node:path";
|
|
8
9
|
//#region src/client/auth.ts
|
|
9
10
|
function createAuthorizationHeader(mode, token) {
|
|
10
11
|
if (mode === "basic") return `Basic ${Buffer.from(`apikey:${token}`, "utf8").toString("base64")}`;
|
|
@@ -174,7 +175,10 @@ function normalizeWorkPackageSummary(resource) {
|
|
|
174
175
|
assignee: getLink(object, "assignee")?.title,
|
|
175
176
|
project: getLink(object, "project")?.title,
|
|
176
177
|
type: getLink(object, "type")?.title,
|
|
177
|
-
href: getLink(object, "self")?.href
|
|
178
|
+
href: getLink(object, "self")?.href,
|
|
179
|
+
updatedAt: stringField(object.updatedAt),
|
|
180
|
+
shortDescription: shortDescription(extractDescription(object.description)),
|
|
181
|
+
attachmentsCount: attachmentsCount(object)
|
|
178
182
|
};
|
|
179
183
|
}
|
|
180
184
|
function normalizeWorkPackageDetail(resource) {
|
|
@@ -182,6 +186,8 @@ function normalizeWorkPackageDetail(resource) {
|
|
|
182
186
|
return {
|
|
183
187
|
...normalizeWorkPackageSummary(object),
|
|
184
188
|
description: extractDescription(object.description),
|
|
189
|
+
shortDescription: shortDescription(extractDescription(object.description)),
|
|
190
|
+
attachmentsCount: attachmentsCount(object),
|
|
185
191
|
lockVersion: numberField(object.lockVersion),
|
|
186
192
|
actions: actionLinks(object)
|
|
187
193
|
};
|
|
@@ -205,6 +211,24 @@ function extractDescription(value) {
|
|
|
205
211
|
const html = typeof object?.html === "string" ? object.html : void 0;
|
|
206
212
|
return raw ?? (html ? html.replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim() : void 0);
|
|
207
213
|
}
|
|
214
|
+
function shortDescription(description) {
|
|
215
|
+
if (!description) return void 0;
|
|
216
|
+
const compact = description.replace(/\s+/g, " ").trim();
|
|
217
|
+
if (compact === "") return void 0;
|
|
218
|
+
return compact.length <= 160 ? compact : `${compact.slice(0, 157)}...`;
|
|
219
|
+
}
|
|
220
|
+
function attachmentsCount(resource) {
|
|
221
|
+
const embeddedAttachments = asObject(asObject(resource._embedded)?.attachments);
|
|
222
|
+
const total = numberField(embeddedAttachments?.total) ?? numberField(embeddedAttachments?.count);
|
|
223
|
+
if (total !== void 0) return total;
|
|
224
|
+
const elements = embeddedAttachments?.elements;
|
|
225
|
+
if (Array.isArray(elements)) return elements.length;
|
|
226
|
+
const direct = asObject(resource.attachments);
|
|
227
|
+
const directTotal = numberField(direct?.total) ?? numberField(direct?.count);
|
|
228
|
+
if (directTotal !== void 0) return directTotal;
|
|
229
|
+
const directElements = direct?.elements;
|
|
230
|
+
if (Array.isArray(directElements)) return directElements.length;
|
|
231
|
+
}
|
|
208
232
|
function stringField(value) {
|
|
209
233
|
return typeof value === "string" ? value : void 0;
|
|
210
234
|
}
|
|
@@ -342,17 +366,14 @@ function buildWorkPackageFilters(options) {
|
|
|
342
366
|
operator: "=",
|
|
343
367
|
values: ["me"]
|
|
344
368
|
} });
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
values: []
|
|
348
|
-
} });
|
|
349
|
-
if (options.status && options.status.trim() !== "") if (options.status === "open") filters.push({ status: {
|
|
369
|
+
const status = options.status?.trim();
|
|
370
|
+
if (options.open || status === "open") filters.push({ status: {
|
|
350
371
|
operator: "o",
|
|
351
372
|
values: []
|
|
352
373
|
} });
|
|
353
|
-
else filters.push({ status: {
|
|
374
|
+
else if (status) filters.push({ status: {
|
|
354
375
|
operator: "=",
|
|
355
|
-
values: [
|
|
376
|
+
values: [status]
|
|
356
377
|
} });
|
|
357
378
|
return filters;
|
|
358
379
|
}
|
|
@@ -380,8 +401,190 @@ async function parseResponse(response) {
|
|
|
380
401
|
return { message: text };
|
|
381
402
|
}
|
|
382
403
|
//#endregion
|
|
383
|
-
//#region src/config.ts
|
|
384
|
-
|
|
404
|
+
//#region src/config/envFile.ts
|
|
405
|
+
var ALLOWED_ENV_KEYS = {
|
|
406
|
+
OPENPROJECT_AUTH_MODE: true,
|
|
407
|
+
OPENPROJECT_DEFAULT_PROJECT: true,
|
|
408
|
+
OPENPROJECT_TOKEN: true,
|
|
409
|
+
OPENPROJECT_URL: true
|
|
410
|
+
};
|
|
411
|
+
function loadEnvFile(path) {
|
|
412
|
+
return parseEnvFile(readFileSync(path, "utf8"));
|
|
413
|
+
}
|
|
414
|
+
function parseEnvFile(content) {
|
|
415
|
+
const env = {};
|
|
416
|
+
const lines = content.split(/\r?\n/);
|
|
417
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
418
|
+
const parsed = parseLine(lines[index] ?? "", index + 1);
|
|
419
|
+
if (!parsed) continue;
|
|
420
|
+
if (ALLOWED_ENV_KEYS[parsed.key] !== true) continue;
|
|
421
|
+
env[parsed.key] = parsed.value;
|
|
422
|
+
}
|
|
423
|
+
return env;
|
|
424
|
+
}
|
|
425
|
+
function parseLine(line, lineNumber) {
|
|
426
|
+
const trimmed = line.trim();
|
|
427
|
+
if (trimmed === "" || trimmed.startsWith("#")) return void 0;
|
|
428
|
+
const equals = trimmed.indexOf("=");
|
|
429
|
+
if (equals <= 0) throw new ConfigurationError(`invalid .env line ${lineNumber}`);
|
|
430
|
+
const key = trimmed.slice(0, equals).trim();
|
|
431
|
+
if (!/^OPENPROJECT_[A-Z_]+$/.test(key)) return void 0;
|
|
432
|
+
return {
|
|
433
|
+
key,
|
|
434
|
+
value: unquoteValue(trimmed.slice(equals + 1).trim(), lineNumber)
|
|
435
|
+
};
|
|
436
|
+
}
|
|
437
|
+
function unquoteValue(value, lineNumber) {
|
|
438
|
+
if (value.startsWith("\"")) {
|
|
439
|
+
if (!value.endsWith("\"") || value.length === 1) throw new ConfigurationError(`unterminated quoted value on .env line ${lineNumber}`);
|
|
440
|
+
return value.slice(1, -1).replace(/\\n/g, "\n").replace(/\\"/g, "\"").replace(/\\\\/g, "\\");
|
|
441
|
+
}
|
|
442
|
+
if (value.startsWith("'")) {
|
|
443
|
+
if (!value.endsWith("'") || value.length === 1) throw new ConfigurationError(`unterminated quoted value on .env line ${lineNumber}`);
|
|
444
|
+
return value.slice(1, -1);
|
|
445
|
+
}
|
|
446
|
+
const hash = value.indexOf(" #");
|
|
447
|
+
return (hash >= 0 ? value.slice(0, hash) : value).trim();
|
|
448
|
+
}
|
|
449
|
+
//#endregion
|
|
450
|
+
//#region src/config/profiles.ts
|
|
451
|
+
var EMPTY_STORE = { profiles: {} };
|
|
452
|
+
function profilesPath(env = process.env) {
|
|
453
|
+
return join(env.XDG_CONFIG_HOME && env.XDG_CONFIG_HOME.trim() !== "" ? env.XDG_CONFIG_HOME : join(homedir(), ".config"), "opctl", "profiles.json");
|
|
454
|
+
}
|
|
455
|
+
function loadProfileStore(path = profilesPath()) {
|
|
456
|
+
if (!existsSync(path)) return EMPTY_STORE;
|
|
457
|
+
const parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
458
|
+
if (!parsed || typeof parsed !== "object") throw new ConfigurationError("profile store is invalid");
|
|
459
|
+
const object = parsed;
|
|
460
|
+
const profiles = object.profiles;
|
|
461
|
+
if (!profiles || typeof profiles !== "object" || Array.isArray(profiles)) throw new ConfigurationError("profile store is invalid");
|
|
462
|
+
return {
|
|
463
|
+
...typeof object.activeProfile === "string" ? { activeProfile: object.activeProfile } : {},
|
|
464
|
+
profiles
|
|
465
|
+
};
|
|
466
|
+
}
|
|
467
|
+
function saveProfileStore(store, path = profilesPath()) {
|
|
468
|
+
mkdirSync(dirname(path), {
|
|
469
|
+
recursive: true,
|
|
470
|
+
mode: 448
|
|
471
|
+
});
|
|
472
|
+
try {
|
|
473
|
+
chmodSync(dirname(path), 448);
|
|
474
|
+
} catch {}
|
|
475
|
+
writeFileSync(path, `${JSON.stringify(store, null, 2)}\n`, { mode: 384 });
|
|
476
|
+
try {
|
|
477
|
+
chmodSync(path, 384);
|
|
478
|
+
} catch {}
|
|
479
|
+
}
|
|
480
|
+
function profileToEnv(profile) {
|
|
481
|
+
if (!profile) return {};
|
|
482
|
+
return {
|
|
483
|
+
...profile.url ? { OPENPROJECT_URL: profile.url } : {},
|
|
484
|
+
...profile.authMode ? { OPENPROJECT_AUTH_MODE: profile.authMode } : {},
|
|
485
|
+
...profile.defaultProject ? { OPENPROJECT_DEFAULT_PROJECT: profile.defaultProject } : {},
|
|
486
|
+
...profile.token ? { OPENPROJECT_TOKEN: profile.token } : {}
|
|
487
|
+
};
|
|
488
|
+
}
|
|
489
|
+
function getProfileEnv(name, env = process.env) {
|
|
490
|
+
const store = loadProfileStore(profilesPath(env));
|
|
491
|
+
const selected = name ?? store.activeProfile;
|
|
492
|
+
if (!selected) return {};
|
|
493
|
+
const profile = store.profiles[selected];
|
|
494
|
+
if (!profile) throw new ConfigurationError(`profile '${selected}' does not exist`);
|
|
495
|
+
return profileToEnv(profile);
|
|
496
|
+
}
|
|
497
|
+
function listProfiles(env = process.env) {
|
|
498
|
+
const store = loadProfileStore(profilesPath(env));
|
|
499
|
+
return Object.entries(store.profiles).sort(([left], [right]) => left.localeCompare(right)).map(([name, profile]) => ({
|
|
500
|
+
name,
|
|
501
|
+
active: name === store.activeProfile,
|
|
502
|
+
profile
|
|
503
|
+
}));
|
|
504
|
+
}
|
|
505
|
+
function setProfile(name, profile, env = process.env) {
|
|
506
|
+
validateProfileName(name);
|
|
507
|
+
const path = profilesPath(env);
|
|
508
|
+
const store = loadProfileStore(path);
|
|
509
|
+
saveProfileStore({
|
|
510
|
+
...store,
|
|
511
|
+
profiles: {
|
|
512
|
+
...store.profiles,
|
|
513
|
+
[name]: cleanProfile(profile)
|
|
514
|
+
}
|
|
515
|
+
}, path);
|
|
516
|
+
}
|
|
517
|
+
function useProfile(name, env = process.env) {
|
|
518
|
+
validateProfileName(name);
|
|
519
|
+
const path = profilesPath(env);
|
|
520
|
+
const store = loadProfileStore(path);
|
|
521
|
+
if (!store.profiles[name]) throw new ConfigurationError(`profile '${name}' does not exist`);
|
|
522
|
+
saveProfileStore({
|
|
523
|
+
...store,
|
|
524
|
+
activeProfile: name
|
|
525
|
+
}, path);
|
|
526
|
+
}
|
|
527
|
+
function unsetProfile(name, env = process.env) {
|
|
528
|
+
validateProfileName(name);
|
|
529
|
+
const path = profilesPath(env);
|
|
530
|
+
const store = loadProfileStore(path);
|
|
531
|
+
const { [name]: _removed, ...profiles } = store.profiles;
|
|
532
|
+
saveProfileStore({
|
|
533
|
+
...store.activeProfile === name ? {} : { activeProfile: store.activeProfile },
|
|
534
|
+
profiles
|
|
535
|
+
}, path);
|
|
536
|
+
}
|
|
537
|
+
function showProfile(name, env = process.env) {
|
|
538
|
+
const store = loadProfileStore(profilesPath(env));
|
|
539
|
+
const selected = name ?? store.activeProfile;
|
|
540
|
+
if (!selected) return {};
|
|
541
|
+
const profile = store.profiles[selected];
|
|
542
|
+
if (!profile) throw new ConfigurationError(`profile '${selected}' does not exist`);
|
|
543
|
+
return {
|
|
544
|
+
name: selected,
|
|
545
|
+
profile
|
|
546
|
+
};
|
|
547
|
+
}
|
|
548
|
+
function redactProfile(profile) {
|
|
549
|
+
return {
|
|
550
|
+
...profile,
|
|
551
|
+
...profile.token ? { token: "<redacted>" } : {}
|
|
552
|
+
};
|
|
553
|
+
}
|
|
554
|
+
function validateProfileName(name) {
|
|
555
|
+
if (!/^[A-Za-z0-9_.-]+$/.test(name)) throw new ConfigurationError("profile name may contain only letters, numbers, '.', '_', and '-'");
|
|
556
|
+
}
|
|
557
|
+
function cleanProfile(profile) {
|
|
558
|
+
return {
|
|
559
|
+
...profile.url && profile.url.trim() !== "" ? { url: profile.url.trim() } : {},
|
|
560
|
+
...profile.authMode && profile.authMode.trim() !== "" ? { authMode: profile.authMode.trim() } : {},
|
|
561
|
+
...profile.defaultProject && profile.defaultProject.trim() !== "" ? { defaultProject: profile.defaultProject.trim() } : {},
|
|
562
|
+
...profile.token && profile.token.trim() !== "" ? { token: profile.token } : {}
|
|
563
|
+
};
|
|
564
|
+
}
|
|
565
|
+
function resolveConfigEnv(processEnv, options = {}) {
|
|
566
|
+
const cwd = options.cwd ?? process.cwd();
|
|
567
|
+
const autoEnvPath = join(cwd, ".env");
|
|
568
|
+
return mergeEnv(options.autoEnv === false || !existsSync(autoEnvPath) ? {} : loadEnvFile(autoEnvPath), options.profile ? {} : getProfileEnv(void 0, processEnv), options.profile ? getProfileEnv(options.profile, processEnv) : {}, options.envFile ? loadEnvFile(resolve(cwd, options.envFile)) : {}, processEnv);
|
|
569
|
+
}
|
|
570
|
+
function loadResolvedConfig(processEnv, options = {}) {
|
|
571
|
+
return loadConfigFromEnv(resolveConfigEnv(processEnv, options));
|
|
572
|
+
}
|
|
573
|
+
function mergeEnv(...layers) {
|
|
574
|
+
const merged = {};
|
|
575
|
+
for (const layer of layers) for (const key of [
|
|
576
|
+
"OPENPROJECT_URL",
|
|
577
|
+
"OPENPROJECT_TOKEN",
|
|
578
|
+
"OPENPROJECT_AUTH_MODE",
|
|
579
|
+
"OPENPROJECT_ALLOW_WRITE",
|
|
580
|
+
"OPENPROJECT_DEFAULT_PROJECT"
|
|
581
|
+
]) {
|
|
582
|
+
const value = layer[key];
|
|
583
|
+
if (value !== void 0) merged[key] = value;
|
|
584
|
+
}
|
|
585
|
+
return merged;
|
|
586
|
+
}
|
|
587
|
+
function loadConfigFromEnv(env) {
|
|
385
588
|
const rawUrl = env.OPENPROJECT_URL;
|
|
386
589
|
const rawToken = env.OPENPROJECT_TOKEN;
|
|
387
590
|
if (!rawUrl || rawUrl.trim() === "") throw new ConfigurationError("OPENPROJECT_URL is required");
|
|
@@ -421,21 +624,43 @@ function cleanOptional(raw) {
|
|
|
421
624
|
}
|
|
422
625
|
//#endregion
|
|
423
626
|
//#region src/commands/context.ts
|
|
424
|
-
function
|
|
627
|
+
function globalConfigOptions(command) {
|
|
628
|
+
const opts = rootCommand(command).opts();
|
|
629
|
+
return {
|
|
630
|
+
...typeof opts.env === "string" ? { envFile: opts.env } : {},
|
|
631
|
+
...opts.env === false ? { autoEnv: false } : {},
|
|
632
|
+
...typeof opts.profile === "string" ? { profile: opts.profile } : {}
|
|
633
|
+
};
|
|
634
|
+
}
|
|
635
|
+
function resolvedEnv(context, command) {
|
|
636
|
+
return resolveConfigEnv(context.env, configOptions(context, command));
|
|
637
|
+
}
|
|
638
|
+
function createClient$1(context, command) {
|
|
425
639
|
return new OpenProjectClient({
|
|
426
|
-
config:
|
|
640
|
+
config: loadResolvedConfig(context.env, command ? configOptions(context, command) : configOptions(context)),
|
|
427
641
|
...context.fetchImpl ? { fetchImpl: context.fetchImpl } : {}
|
|
428
642
|
});
|
|
429
643
|
}
|
|
430
|
-
function writeOutput(context, value, json, renderText) {
|
|
431
|
-
context.stdout.write(json ? stableJson(value, context.env.OPENPROJECT_TOKEN) : renderText());
|
|
644
|
+
function writeOutput(context, value, json, renderText, token) {
|
|
645
|
+
context.stdout.write(json ? stableJson(value, token ?? context.env.OPENPROJECT_TOKEN) : renderText());
|
|
646
|
+
}
|
|
647
|
+
function configOptions(context, command) {
|
|
648
|
+
return {
|
|
649
|
+
...context.cwd ? { cwd: context.cwd } : {},
|
|
650
|
+
...command ? globalConfigOptions(command) : {}
|
|
651
|
+
};
|
|
652
|
+
}
|
|
653
|
+
function rootCommand(command) {
|
|
654
|
+
let current = command;
|
|
655
|
+
while (current.parent) current = current.parent;
|
|
656
|
+
return current;
|
|
432
657
|
}
|
|
433
658
|
//#endregion
|
|
434
659
|
//#region src/commands/apiRoot.ts
|
|
435
660
|
function registerApiRoot(program, context) {
|
|
436
|
-
program.command("api-root").description("Show compact OpenProject API root links").option("--json", "emit JSON").action(async (options) => {
|
|
437
|
-
const links = compactLinks(await createClient$1(context).getApiRoot());
|
|
438
|
-
context.stdout.write(options.json ? stableJson(links, context.
|
|
661
|
+
program.command("api-root").description("Show compact OpenProject API root links").option("--json", "emit JSON").action(async (options, command) => {
|
|
662
|
+
const links = compactLinks(await createClient$1(context, command).getApiRoot());
|
|
663
|
+
context.stdout.write(options.json ? stableJson(links, resolvedEnv(context, command).OPENPROJECT_TOKEN) : renderKeyValue(links));
|
|
439
664
|
});
|
|
440
665
|
}
|
|
441
666
|
function compactLinks(root) {
|
|
@@ -450,8 +675,8 @@ function compactLinks(root) {
|
|
|
450
675
|
//#endregion
|
|
451
676
|
//#region src/commands/me.ts
|
|
452
677
|
function registerMe(program, context) {
|
|
453
|
-
program.command("me").description("Show the authenticated OpenProject user").option("--json", "emit JSON").action(async (options) => {
|
|
454
|
-
const me = await createClient$1(context).getMe();
|
|
678
|
+
program.command("me").description("Show the authenticated OpenProject user").option("--json", "emit JSON").action(async (options, command) => {
|
|
679
|
+
const me = await createClient$1(context, command).getMe();
|
|
455
680
|
writeOutput(context, me, Boolean(options.json), () => renderKeyValue(me));
|
|
456
681
|
});
|
|
457
682
|
}
|
|
@@ -472,8 +697,8 @@ function cell(value) {
|
|
|
472
697
|
//#endregion
|
|
473
698
|
//#region src/commands/projects.ts
|
|
474
699
|
function registerProjects(program, context) {
|
|
475
|
-
program.command("projects").description("List visible OpenProject projects").option("--json", "emit JSON").option("--page-size <n>", "page size", Number).action(async (options) => {
|
|
476
|
-
const projects = await createClient$1(context).listProjects(options.pageSize === void 0 ? {} : { pageSize: options.pageSize });
|
|
700
|
+
program.command("projects").description("List visible OpenProject projects").option("--json", "emit JSON").option("--page-size <n>", "page size", Number).action(async (options, command) => {
|
|
701
|
+
const projects = await createClient$1(context, command).listProjects(options.pageSize === void 0 ? {} : { pageSize: options.pageSize });
|
|
477
702
|
writeOutput(context, projects, Boolean(options.json), () => renderTable(projects.elements, [
|
|
478
703
|
"id",
|
|
479
704
|
"identifier",
|
|
@@ -545,66 +770,270 @@ if (import.meta.url === `file://${process.argv[1]}`) pullOpenApiSpec({ stdout: p
|
|
|
545
770
|
//#endregion
|
|
546
771
|
//#region src/commands/spec.ts
|
|
547
772
|
function registerSpec(program, context) {
|
|
548
|
-
program.command("spec").description("OpenAPI spec utilities").command("pull").description("Download OpenProject /api/v3/spec.json safely").action(async () => {
|
|
773
|
+
program.command("spec").description("OpenAPI spec utilities").command("pull").description("Download OpenProject /api/v3/spec.json safely").action(async (_options, command) => {
|
|
549
774
|
await pullOpenApiSpec({
|
|
550
|
-
env: context
|
|
775
|
+
env: resolvedEnv(context, command),
|
|
551
776
|
stdout: context.stdout,
|
|
552
777
|
...context.fetchImpl ? { fetchImpl: context.fetchImpl } : {}
|
|
553
778
|
});
|
|
554
779
|
});
|
|
555
780
|
}
|
|
556
781
|
//#endregion
|
|
782
|
+
//#region src/output/fields.ts
|
|
783
|
+
var DEFAULT_COMPACT_FIELDS = [
|
|
784
|
+
"id",
|
|
785
|
+
"subject",
|
|
786
|
+
"status",
|
|
787
|
+
"assignee",
|
|
788
|
+
"updatedAt"
|
|
789
|
+
];
|
|
790
|
+
var DEFAULT_TABLE_FIELDS = [
|
|
791
|
+
"id",
|
|
792
|
+
"subject",
|
|
793
|
+
"status",
|
|
794
|
+
"assignee",
|
|
795
|
+
"project",
|
|
796
|
+
"updatedAt"
|
|
797
|
+
];
|
|
798
|
+
var DEFAULT_CHECK_FIELDS = [
|
|
799
|
+
"id",
|
|
800
|
+
"subject",
|
|
801
|
+
"status",
|
|
802
|
+
"assignee",
|
|
803
|
+
"shortDescription",
|
|
804
|
+
"attachmentsCount"
|
|
805
|
+
];
|
|
806
|
+
var DETAIL_FIELDS = [
|
|
807
|
+
"id",
|
|
808
|
+
"subject",
|
|
809
|
+
"status",
|
|
810
|
+
"type",
|
|
811
|
+
"assignee",
|
|
812
|
+
"project",
|
|
813
|
+
"href",
|
|
814
|
+
"updatedAt",
|
|
815
|
+
"description",
|
|
816
|
+
"shortDescription",
|
|
817
|
+
"attachmentsCount",
|
|
818
|
+
"lockVersion"
|
|
819
|
+
];
|
|
820
|
+
var SUPPORTED_FIELDS = {
|
|
821
|
+
assignee: true,
|
|
822
|
+
attachmentsCount: true,
|
|
823
|
+
description: true,
|
|
824
|
+
href: true,
|
|
825
|
+
id: true,
|
|
826
|
+
lockVersion: true,
|
|
827
|
+
project: true,
|
|
828
|
+
shortDescription: true,
|
|
829
|
+
status: true,
|
|
830
|
+
subject: true,
|
|
831
|
+
type: true,
|
|
832
|
+
updatedAt: true
|
|
833
|
+
};
|
|
834
|
+
var FIELD_ALIASES = {
|
|
835
|
+
title: "subject",
|
|
836
|
+
url: "href"
|
|
837
|
+
};
|
|
838
|
+
function parseOutputMode(options) {
|
|
839
|
+
if ([
|
|
840
|
+
options.json,
|
|
841
|
+
options.jsonl,
|
|
842
|
+
options.table,
|
|
843
|
+
options.compact,
|
|
844
|
+
options.rawJson
|
|
845
|
+
].filter(Boolean).length > 1) throw new OpctlError("choose only one output mode flag", EXIT_CODES.validation);
|
|
846
|
+
if (options.rawJson) return "rawJson";
|
|
847
|
+
if (options.json) return "json";
|
|
848
|
+
if (options.jsonl) return "jsonl";
|
|
849
|
+
if (options.compact) return "compact";
|
|
850
|
+
if (options.table) return "table";
|
|
851
|
+
return "text";
|
|
852
|
+
}
|
|
853
|
+
function parseFields(raw, defaults) {
|
|
854
|
+
if (!raw || raw.trim() === "") return defaults;
|
|
855
|
+
const fields = [];
|
|
856
|
+
for (const part of raw.split(",")) {
|
|
857
|
+
const trimmed = part.trim();
|
|
858
|
+
if (trimmed === "") continue;
|
|
859
|
+
const field = FIELD_ALIASES[trimmed] ?? trimmed;
|
|
860
|
+
if (!isSupportedField(field)) throw new OpctlError(`unknown work package field '${trimmed}'. Supported fields: ${Object.keys(SUPPORTED_FIELDS).join(",")}`, EXIT_CODES.validation);
|
|
861
|
+
fields.push(field);
|
|
862
|
+
}
|
|
863
|
+
if (fields.length === 0) throw new OpctlError("--fields must include at least one field", EXIT_CODES.validation);
|
|
864
|
+
return fields;
|
|
865
|
+
}
|
|
866
|
+
function projectFields(value, fields) {
|
|
867
|
+
const projected = {};
|
|
868
|
+
for (const field of fields) projected[field] = value[field];
|
|
869
|
+
return projected;
|
|
870
|
+
}
|
|
871
|
+
function projectRows(rows, fields) {
|
|
872
|
+
return rows.map((row) => projectFields(row, fields));
|
|
873
|
+
}
|
|
874
|
+
function isSupportedField(field) {
|
|
875
|
+
return SUPPORTED_FIELDS[field] === true;
|
|
876
|
+
}
|
|
877
|
+
//#endregion
|
|
557
878
|
//#region src/commands/workPackages.ts
|
|
558
879
|
function registerWorkPackages(program, context) {
|
|
559
880
|
const wp = program.command("wp").description("Work package commands");
|
|
560
|
-
wp.command("get").description("Get one work
|
|
561
|
-
const
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
881
|
+
wp.command("get").description("Get one or more work packages").argument("[ids...]", "work package ids").option("--ids <csv>", "comma-separated work package ids").option("--fields <csv>", "comma-separated output fields").option("--table", "emit table output").option("--compact", "emit compact triage table output").option("--json", "emit normalized JSON").option("--jsonl", "emit one JSON object per line").option("--raw-json", "emit raw OpenProject JSON; single work package only").action(async (ids, options, command) => {
|
|
882
|
+
const numericIds = parseIds(ids, options.ids);
|
|
883
|
+
if (numericIds.length === 0) throw new OpctlError("at least one work package id is required", EXIT_CODES.validation);
|
|
884
|
+
const mode = parseOutputMode(options);
|
|
885
|
+
if (mode === "rawJson" && numericIds.length !== 1) throw new OpctlError("--raw-json supports exactly one work package id", EXIT_CODES.validation);
|
|
886
|
+
if (mode !== "rawJson") parseFields(options.fields, mode === "compact" ? DEFAULT_COMPACT_FIELDS : DETAIL_FIELDS);
|
|
887
|
+
const client = createClient$1(context, command);
|
|
888
|
+
const token = resolvedEnv(context, command).OPENPROJECT_TOKEN;
|
|
889
|
+
if (mode === "rawJson") {
|
|
890
|
+
context.stdout.write(stableJson(await client.getWorkPackageRaw(numericIds[0]), token));
|
|
565
891
|
return;
|
|
566
892
|
}
|
|
567
|
-
const
|
|
568
|
-
|
|
893
|
+
const workPackages = [];
|
|
894
|
+
for (const id of numericIds) workPackages.push(await client.getWorkPackage(id));
|
|
895
|
+
writeWorkPackageOutput(context, workPackages, options, mode, numericIds.length === 1 && !options.ids, DETAIL_FIELDS, token);
|
|
569
896
|
});
|
|
570
|
-
wp.command("
|
|
571
|
-
const
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
]));
|
|
897
|
+
wp.command("check").description("Read a triage-oriented issue list for one or more work packages").argument("[ids...]", "work package ids").option("--ids <csv>", "comma-separated work package ids").option("--fields <csv>", "comma-separated output fields").option("--table", "emit table output").option("--compact", "emit compact table output").option("--json", "emit JSON").option("--jsonl", "emit one JSON object per line").action(async (ids, options, command) => {
|
|
898
|
+
const numericIds = parseIds(ids, options.ids);
|
|
899
|
+
if (numericIds.length === 0) throw new OpctlError("at least one work package id is required", EXIT_CODES.validation);
|
|
900
|
+
const mode = parseOutputMode(options);
|
|
901
|
+
parseFields(options.fields, mode === "compact" ? DEFAULT_COMPACT_FIELDS : DEFAULT_CHECK_FIELDS);
|
|
902
|
+
const client = createClient$1(context, command);
|
|
903
|
+
const workPackages = [];
|
|
904
|
+
for (const id of numericIds) workPackages.push(await client.getWorkPackage(id));
|
|
905
|
+
writeWorkPackageOutput(context, workPackages, options, mode, false, DEFAULT_CHECK_FIELDS, resolvedEnv(context, command).OPENPROJECT_TOKEN);
|
|
580
906
|
});
|
|
581
|
-
wp.command("
|
|
582
|
-
|
|
583
|
-
writeOutput(context, result, Boolean(options.json), () => renderTable(result.elements, [
|
|
584
|
-
"id",
|
|
585
|
-
"subject",
|
|
586
|
-
"status",
|
|
587
|
-
"assignee",
|
|
588
|
-
"project",
|
|
589
|
-
"href"
|
|
590
|
-
]));
|
|
907
|
+
wp.command("search").description("Search work packages").option("--json", "emit JSON").option("--jsonl", "emit one JSON object per line").option("--table", "emit table output").option("--compact", "emit compact table output").option("--fields <csv>", "comma-separated output fields").option("--project <identifier-or-id>", "project identifier or id").option("--subject <text>", "subject contains text").option("--assignee-me", "filter to current user").option("--status <id-or-open>", "status id, or open").option("--open", "filter to open work packages").option("--page-size <n>", "page size", Number).action(async (options, command) => {
|
|
908
|
+
writeCollectionOutput(context, await createClient$1(context, command).searchWorkPackages(options), options, resolvedEnv(context, command).OPENPROJECT_TOKEN);
|
|
591
909
|
});
|
|
592
|
-
wp.command("
|
|
593
|
-
|
|
594
|
-
writeOutput(context, result, Boolean(options.json), () => renderKeyValue(result));
|
|
910
|
+
wp.command("mine").description("List open work packages assigned to the authenticated user").option("--json", "emit JSON").option("--jsonl", "emit one JSON object per line").option("--table", "emit table output").option("--compact", "emit compact table output").option("--fields <csv>", "comma-separated output fields").option("--project <identifier-or-id>", "project identifier or id").option("--open", "filter to open work packages").option("--page-size <n>", "page size", Number).action(async (options, command) => {
|
|
911
|
+
writeCollectionOutput(context, await createClient$1(context, command).mine(options), options, resolvedEnv(context, command).OPENPROJECT_TOKEN);
|
|
595
912
|
});
|
|
913
|
+
wp.command("comment").description("Add a comment to a work package; requires OPENPROJECT_ALLOW_WRITE=1").argument("<id>", "work package id").argument("[message...]", "comment message").option("--dry-run", "print intended mutation without posting").option("--json", "emit JSON").action(async (id, messageParts, options, command) => {
|
|
914
|
+
const result = await createClient$1(context, command).commentWorkPackage(parseId(id), messageParts.join(" "), Boolean(options.dryRun));
|
|
915
|
+
writeOutput(context, result, Boolean(options.json), () => renderKeyValue(result), resolvedEnv(context, command).OPENPROJECT_TOKEN);
|
|
916
|
+
});
|
|
917
|
+
}
|
|
918
|
+
function writeCollectionOutput(context, result, options, token) {
|
|
919
|
+
const mode = parseOutputMode(options);
|
|
920
|
+
const defaults = mode === "compact" ? DEFAULT_COMPACT_FIELDS : DEFAULT_TABLE_FIELDS;
|
|
921
|
+
const fields = parseFields(options.fields, defaults);
|
|
922
|
+
const elements = projectRows(result.elements, fields);
|
|
923
|
+
if (mode === "jsonl") {
|
|
924
|
+
context.stdout.write(elements.map((element) => JSON.stringify(element)).join("\n") + (elements.length > 0 ? "\n" : ""));
|
|
925
|
+
return;
|
|
926
|
+
}
|
|
927
|
+
if (mode === "json") {
|
|
928
|
+
const output = options.fields ? {
|
|
929
|
+
...result,
|
|
930
|
+
elements
|
|
931
|
+
} : result;
|
|
932
|
+
context.stdout.write(stableJson(output, token));
|
|
933
|
+
return;
|
|
934
|
+
}
|
|
935
|
+
context.stdout.write(renderTable(elements, fields));
|
|
936
|
+
}
|
|
937
|
+
function writeWorkPackageOutput(context, workPackages, options, mode, singleObject, defaultFields, token) {
|
|
938
|
+
const fields = parseFields(options.fields, mode === "compact" ? DEFAULT_COMPACT_FIELDS : defaultFields);
|
|
939
|
+
const projected = options.fields || mode === "table" || mode === "compact" || mode === "jsonl" ? projectRows(workPackages, fields) : workPackages;
|
|
940
|
+
if (mode === "jsonl") {
|
|
941
|
+
const rows = projectRows(workPackages, fields);
|
|
942
|
+
context.stdout.write(rows.map((row) => JSON.stringify(row)).join("\n") + (rows.length > 0 ? "\n" : ""));
|
|
943
|
+
return;
|
|
944
|
+
}
|
|
945
|
+
if (mode === "json") {
|
|
946
|
+
context.stdout.write(stableJson(singleObject ? projected[0] : projected, token));
|
|
947
|
+
return;
|
|
948
|
+
}
|
|
949
|
+
if (singleObject && mode === "text" && !options.fields) {
|
|
950
|
+
context.stdout.write(renderKeyValue(workPackages[0]));
|
|
951
|
+
return;
|
|
952
|
+
}
|
|
953
|
+
context.stdout.write(renderTable(projectRows(workPackages, fields), fields));
|
|
954
|
+
}
|
|
955
|
+
function parseIds(positionals, csv) {
|
|
956
|
+
const seen = /* @__PURE__ */ new Set();
|
|
957
|
+
const ids = [];
|
|
958
|
+
for (const raw of [...positionals, ...splitCsv(csv)]) {
|
|
959
|
+
const id = parseId(raw);
|
|
960
|
+
if (seen.has(id)) continue;
|
|
961
|
+
seen.add(id);
|
|
962
|
+
ids.push(id);
|
|
963
|
+
}
|
|
964
|
+
return ids;
|
|
965
|
+
}
|
|
966
|
+
function splitCsv(csv) {
|
|
967
|
+
if (!csv) return [];
|
|
968
|
+
return csv.split(",").map((part) => part.trim()).filter((part) => part !== "");
|
|
596
969
|
}
|
|
597
970
|
function parseId(id) {
|
|
598
971
|
const parsed = Number(id);
|
|
599
972
|
if (!Number.isInteger(parsed) || parsed < 1) throw new OpctlError("work package id must be a positive integer", EXIT_CODES.validation);
|
|
600
973
|
return parsed;
|
|
601
974
|
}
|
|
975
|
+
//#endregion
|
|
976
|
+
//#region src/commands/profile.ts
|
|
977
|
+
function registerProfile(program, context) {
|
|
978
|
+
const profile = program.command("profile").description("Saved non-write OpenProject connection profiles");
|
|
979
|
+
profile.command("list").description("List saved profiles").option("--json", "emit JSON").action((options) => {
|
|
980
|
+
const rows = listProfiles(context.env).map((entry) => ({
|
|
981
|
+
name: entry.name,
|
|
982
|
+
active: entry.active ? "yes" : "",
|
|
983
|
+
...redactProfile(entry.profile)
|
|
984
|
+
}));
|
|
985
|
+
context.stdout.write(options.json ? stableJson(rows) : renderTable(rows, [
|
|
986
|
+
"name",
|
|
987
|
+
"active",
|
|
988
|
+
"url",
|
|
989
|
+
"authMode",
|
|
990
|
+
"defaultProject",
|
|
991
|
+
"token"
|
|
992
|
+
]));
|
|
993
|
+
});
|
|
994
|
+
profile.command("show").description("Show a saved profile with secrets redacted").argument("[name]", "profile name; defaults to active profile").option("--json", "emit JSON").action((name, options) => {
|
|
995
|
+
const result = showProfile(name, context.env);
|
|
996
|
+
const output = result.profile ? {
|
|
997
|
+
name: result.name,
|
|
998
|
+
...redactProfile(result.profile)
|
|
999
|
+
} : { name: void 0 };
|
|
1000
|
+
context.stdout.write(options.json ? stableJson(output) : renderKeyValue(output));
|
|
1001
|
+
});
|
|
1002
|
+
profile.command("set").description("Create or replace a saved profile").argument("<name>", "profile name").option("--url <url>", "OpenProject base URL").option("--auth-mode <mode>", "auth mode: bearer or basic").option("--default-project <id>", "default project identifier or id").option("--token <token>", "OpenProject API token; stored in a 0600 profile file").option("--json", "emit JSON").action((name, options) => {
|
|
1003
|
+
const profileValue = {
|
|
1004
|
+
...options.url ? { url: options.url } : {},
|
|
1005
|
+
...options.authMode ? { authMode: options.authMode } : {},
|
|
1006
|
+
...options.defaultProject ? { defaultProject: options.defaultProject } : {},
|
|
1007
|
+
...options.token ? { token: options.token } : {}
|
|
1008
|
+
};
|
|
1009
|
+
setProfile(name, profileValue, context.env);
|
|
1010
|
+
const output = {
|
|
1011
|
+
name,
|
|
1012
|
+
...redactProfile(profileValue)
|
|
1013
|
+
};
|
|
1014
|
+
context.stdout.write(options.json ? stableJson(output) : `${name}\n`);
|
|
1015
|
+
});
|
|
1016
|
+
profile.command("use").description("Set the active profile").argument("<name>", "profile name").option("--json", "emit JSON").action((name, options) => {
|
|
1017
|
+
useProfile(name, context.env);
|
|
1018
|
+
const output = { activeProfile: name };
|
|
1019
|
+
context.stdout.write(options.json ? stableJson(output) : `active profile: ${name}\n`);
|
|
1020
|
+
});
|
|
1021
|
+
profile.command("unset").description("Delete a saved profile").argument("<name>", "profile name").option("--json", "emit JSON").action((name, options) => {
|
|
1022
|
+
unsetProfile(name, context.env);
|
|
1023
|
+
const output = { unset: name };
|
|
1024
|
+
context.stdout.write(options.json ? stableJson(output) : `unset profile: ${name}\n`);
|
|
1025
|
+
});
|
|
1026
|
+
}
|
|
602
1027
|
var package_default = {
|
|
603
1028
|
name: "opctl",
|
|
604
|
-
version: "0.1.
|
|
1029
|
+
version: "0.1.4",
|
|
605
1030
|
description: "Conservative local CLI bridge for OpenProject API v3",
|
|
606
1031
|
type: "module",
|
|
607
|
-
|
|
1032
|
+
repository: {
|
|
1033
|
+
"type": "git",
|
|
1034
|
+
"url": "git+https://github.com/hewel/op-cli.git"
|
|
1035
|
+
},
|
|
1036
|
+
bin: { "opctl": "dist/cli.js" },
|
|
608
1037
|
files: ["dist"],
|
|
609
1038
|
scripts: {
|
|
610
1039
|
"dev": "tsx src/cli.ts",
|
|
@@ -637,7 +1066,7 @@ var package_default = {
|
|
|
637
1066
|
//#region src/cli.ts
|
|
638
1067
|
function buildProgram(context) {
|
|
639
1068
|
const program = new Command();
|
|
640
|
-
program.name("opctl").description("Conservative local CLI bridge for OpenProject API v3").version(package_default.version).showHelpAfterError().configureOutput({
|
|
1069
|
+
program.name("opctl").description("Conservative local CLI bridge for OpenProject API v3").version(package_default.version).option("--env <path>", "load OpenProject configuration from dotenv-style file").option("--no-env", "disable automatic .env loading from the current working directory").option("--profile <name>", "use a saved profile for this invocation").showHelpAfterError().configureOutput({
|
|
641
1070
|
writeOut: (text) => context.stdout.write(text),
|
|
642
1071
|
writeErr: (text) => context.stderr.write(text)
|
|
643
1072
|
});
|
|
@@ -645,6 +1074,7 @@ function buildProgram(context) {
|
|
|
645
1074
|
registerApiRoot(program, context);
|
|
646
1075
|
registerProjects(program, context);
|
|
647
1076
|
registerWorkPackages(program, context);
|
|
1077
|
+
registerProfile(program, context);
|
|
648
1078
|
registerSpec(program, context);
|
|
649
1079
|
return program;
|
|
650
1080
|
}
|
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.js","names":[],"sources":["../src/client/auth.ts","../src/client/errors.ts","../src/output/json.ts","../src/client/hal.ts","../src/output/text.ts","../src/client/pagination.ts","../src/client/openProjectClient.ts","../src/config.ts","../src/commands/context.ts","../src/commands/apiRoot.ts","../src/commands/me.ts","../src/output/table.ts","../src/commands/projects.ts","../scripts/pull-openapi-spec.ts","../src/commands/spec.ts","../src/commands/workPackages.ts","../package.json","../src/cli.ts"],"sourcesContent":["import type { AuthMode } from \"../config.js\";\n\nexport function createAuthorizationHeader(mode: AuthMode, token: string): string {\n if (mode === \"basic\") {\n return `Basic ${Buffer.from(`apikey:${token}`, \"utf8\").toString(\"base64\")}`;\n }\n return `Bearer ${token}`;\n}\n\nexport function redactSecrets(value: string, token?: string): string {\n let redacted = value.replace(/Authorization:\\s*(Bearer|Basic)\\s+[^\\s,}]+/gi, \"Authorization: <redacted>\");\n redacted = redacted.replace(/\"Authorization\"\\s*:\\s*\"[^\"]+\"/gi, '\"Authorization\":\"<redacted>\"');\n if (token && token !== \"\") redacted = redacted.split(token).join(\"<redacted>\");\n return redacted;\n}\n","export const EXIT_CODES = {\n success: 0,\n general: 1,\n config: 2,\n auth: 3,\n notFound: 4,\n validation: 5,\n writeBlocked: 6,\n network: 7,\n openapi: 8,\n} as const;\n\nexport type ExitCode = (typeof EXIT_CODES)[keyof typeof EXIT_CODES];\n\nexport class OpctlError extends Error {\n public readonly exitCode: ExitCode;\n public readonly details: unknown;\n\n public constructor(message: string, exitCode: ExitCode = EXIT_CODES.general, details?: unknown) {\n super(message);\n this.name = \"OpctlError\";\n this.exitCode = exitCode;\n this.details = details;\n }\n}\n\nexport class ConfigurationError extends OpctlError {\n public constructor(message: string) {\n super(message, EXIT_CODES.config);\n this.name = \"ConfigurationError\";\n }\n}\n\nexport class WriteBlockedError extends OpctlError {\n public constructor() {\n super(\"OpenProject write blocked: set OPENPROJECT_ALLOW_WRITE=1 to enable write commands\", EXIT_CODES.writeBlocked);\n this.name = \"WriteBlockedError\";\n }\n}\n\nexport class NetworkError extends OpctlError {\n public constructor(message: string) {\n super(message, EXIT_CODES.network);\n this.name = \"NetworkError\";\n }\n}\n\nexport class OpenApiGenerationError extends OpctlError {\n public constructor(message: string) {\n super(message, EXIT_CODES.openapi);\n this.name = \"OpenApiGenerationError\";\n }\n}\n\nexport class OpenProjectHttpError extends OpctlError {\n public readonly status: number;\n public readonly responseBody: unknown;\n\n public constructor(status: number, responseBody: unknown) {\n super(httpStatusMessage(status, responseBody), exitCodeForStatus(status), responseBody);\n this.name = \"OpenProjectHttpError\";\n this.status = status;\n this.responseBody = responseBody;\n }\n}\n\nexport function exitCodeForStatus(status: number): ExitCode {\n if (status === 401 || status === 403) return EXIT_CODES.auth;\n if (status === 404) return EXIT_CODES.notFound;\n if (status === 422) return EXIT_CODES.validation;\n return EXIT_CODES.general;\n}\n\nexport function httpStatusMessage(status: number, body: unknown): string {\n if (status === 401) return \"authentication failed\";\n if (status === 403) return \"authenticated OpenProject user lacks permission\";\n if (status === 404) return \"resource not found or not visible to this user\";\n if (status === 409) return \"possible stale lockVersion or concurrent modification\";\n if (status === 422) return `validation failed${validationDetail(body)}`;\n return `OpenProject request failed with HTTP ${status}`;\n}\n\nfunction validationDetail(body: unknown): string {\n if (!body || typeof body !== \"object\") return \"\";\n const message = \"message\" in body && typeof body.message === \"string\" ? body.message : undefined;\n const errorIdentifier = \"errorIdentifier\" in body && typeof body.errorIdentifier === \"string\" ? body.errorIdentifier : undefined;\n const errors = \"_embedded\" in body ? body._embedded : undefined;\n const parts = [message, errorIdentifier, typeof errors === \"object\" && errors !== null ? JSON.stringify(errors) : undefined].filter(Boolean);\n return parts.length === 0 ? \"\" : `: ${parts.join(\"; \")}`;\n}\n\nexport function toOpctlError(error: unknown): OpctlError {\n if (error instanceof OpctlError) return error;\n if (error instanceof Error) return new OpctlError(error.message);\n return new OpctlError(\"unexpected failure\");\n}\n","import { redactSecrets } from \"../client/auth.js\";\n\nexport function stableJson(value: unknown, token?: string): string {\n return `${redactSecrets(JSON.stringify(sortForJson(value), null, 2), token)}\\n`;\n}\n\nfunction sortForJson(value: unknown): unknown {\n if (Array.isArray(value)) return value.map(sortForJson);\n if (!value || typeof value !== \"object\") return value;\n const sorted: Record<string, unknown> = {};\n for (const key of Object.keys(value).sort()) sorted[key] = sortForJson((value as Record<string, unknown>)[key]);\n return sorted;\n}\n","import type { LinkSummary, ProjectSummary, UserSummary, WorkPackageDetail, WorkPackageSummary } from \"../types/domain.js\";\n\ntype HalObject = Record<string, unknown>;\n\nexport function asObject(value: unknown): HalObject | undefined {\n return value && typeof value === \"object\" ? (value as HalObject) : undefined;\n}\n\nexport function getLink(resource: unknown, name: string): LinkSummary | undefined {\n const object = asObject(resource);\n const links = asObject(object?._links);\n const raw = links?.[name];\n const link = Array.isArray(raw) ? asObject(raw[0]) : asObject(raw);\n if (!link) return undefined;\n const href = typeof link.href === \"string\" ? link.href : undefined;\n const title = typeof link.title === \"string\" ? link.title : undefined;\n const method = typeof link.method === \"string\" ? link.method : undefined;\n if (!href && !title && !method) return undefined;\n return { ...(href ? { href } : {}), ...(title ? { title } : {}), ...(method ? { method } : {}) };\n}\n\nexport function requireLinkHref(resource: unknown, name: string): string | undefined {\n return getLink(resource, name)?.href;\n}\n\nexport function collectionElements(resource: unknown): unknown[] {\n const embedded = asObject(asObject(resource)?._embedded);\n const elements = embedded?.elements;\n return Array.isArray(elements) ? elements : [];\n}\n\nexport function collectionTotal(resource: unknown): number | undefined {\n const total = asObject(resource)?.total;\n return typeof total === \"number\" ? total : undefined;\n}\n\nexport function normalizeUser(resource: unknown): UserSummary {\n const object = asObject(resource) ?? {};\n return {\n id: numberField(object.id),\n name: stringField(object.name),\n login: stringField(object.login),\n email: stringField(object.email),\n href: getLink(object, \"self\")?.href,\n };\n}\n\nexport function normalizeProject(resource: unknown): ProjectSummary {\n const object = asObject(resource) ?? {};\n return {\n id: numberField(object.id),\n identifier: stringField(object.identifier),\n name: stringField(object.name),\n href: getLink(object, \"self\")?.href,\n };\n}\n\nexport function normalizeWorkPackageSummary(resource: unknown): WorkPackageSummary {\n const object = asObject(resource) ?? {};\n return {\n id: numberField(object.id),\n subject: stringField(object.subject),\n status: getLink(object, \"status\")?.title,\n assignee: getLink(object, \"assignee\")?.title,\n project: getLink(object, \"project\")?.title,\n type: getLink(object, \"type\")?.title,\n href: getLink(object, \"self\")?.href,\n };\n}\n\nexport function normalizeWorkPackageDetail(resource: unknown): WorkPackageDetail {\n const object = asObject(resource) ?? {};\n return {\n ...normalizeWorkPackageSummary(object),\n description: extractDescription(object.description),\n lockVersion: numberField(object.lockVersion),\n actions: actionLinks(object),\n };\n}\n\nexport function actionLinks(resource: unknown): Record<string, LinkSummary> {\n const links = asObject(asObject(resource)?._links) ?? {};\n const actions: Record<string, LinkSummary> = {};\n for (const [name, value] of Object.entries(links)) {\n const link = Array.isArray(value) ? asObject(value[0]) : asObject(value);\n if (!link) continue;\n const method = typeof link.method === \"string\" ? link.method : undefined;\n if (!method && !name.startsWith(\"add\") && !name.includes(\"update\") && !name.includes(\"delete\")) continue;\n const summary = getLink(resource, name);\n if (summary) actions[name] = summary;\n }\n return actions;\n}\n\nexport function extractDescription(value: unknown): string | undefined {\n if (typeof value === \"string\") return value;\n const object = asObject(value);\n const raw = typeof object?.raw === \"string\" ? object.raw : undefined;\n const html = typeof object?.html === \"string\" ? object.html : undefined;\n return raw ?? (html ? html.replace(/<[^>]+>/g, \" \").replace(/\\s+/g, \" \").trim() : undefined);\n}\n\nfunction stringField(value: unknown): string | undefined {\n return typeof value === \"string\" ? value : undefined;\n}\n\nfunction numberField(value: unknown): number | undefined {\n return typeof value === \"number\" ? value : undefined;\n}\n","export function renderKeyValue(value: Record<string, unknown>): string {\n return `${Object.entries(value)\n .filter(([, item]) => item !== undefined)\n .map(([key, item]) => `${key}: ${String(item)}`)\n .join(\"\\n\")}\\n`;\n}\n","import { collectionElements, collectionTotal } from \"./hal.js\";\n\nexport interface PageOptions {\n readonly pageSize?: number;\n}\n\nexport interface NormalizedCollection<T> {\n readonly elements: readonly T[];\n readonly total?: number;\n readonly count: number;\n}\n\nexport function normalizePageSize(value: unknown, fallback = 25): number {\n if (value === undefined || value === null || value === \"\") return fallback;\n const parsed = typeof value === \"number\" ? value : Number(value);\n if (!Number.isInteger(parsed) || parsed < 1 || parsed > 100) throw new Error(\"page size must be an integer between 1 and 100\");\n return parsed;\n}\n\nexport function normalizeCollection<T>(resource: unknown, mapper: (value: unknown) => T): NormalizedCollection<T> {\n const elements = collectionElements(resource).map(mapper);\n const total = collectionTotal(resource);\n return { elements, ...(total === undefined ? {} : { total }), count: elements.length };\n}\n","import createClient, { type Client } from \"openapi-fetch\";\nimport { createAuthorizationHeader } from \"./auth.js\";\nimport { OpenProjectHttpError, NetworkError, WriteBlockedError, OpctlError, EXIT_CODES } from \"./errors.js\";\nimport { normalizeCollection, normalizePageSize, type NormalizedCollection } from \"./pagination.js\";\nimport { getLink, normalizeProject, normalizeUser, normalizeWorkPackageDetail, normalizeWorkPackageSummary, requireLinkHref } from \"./hal.js\";\nimport type { OpctlConfig } from \"../config.js\";\nimport type { paths } from \"../generated/openproject.js\";\nimport type { CommentResult, ProjectSummary, UserSummary, WorkPackageDetail, WorkPackageSummary } from \"../types/domain.js\";\n\nexport interface SearchWorkPackagesOptions {\n readonly project?: string;\n readonly subject?: string;\n readonly assigneeMe?: boolean;\n readonly status?: string;\n readonly open?: boolean;\n readonly pageSize?: number;\n}\n\nexport interface OpenProjectClientOptions {\n readonly config: OpctlConfig;\n readonly fetchImpl?: typeof fetch;\n readonly timeoutMs?: number;\n}\n\nexport class OpenProjectClient {\n private readonly config: OpctlConfig;\n private readonly fetchImpl: typeof fetch;\n private readonly timeoutMs: number;\n private readonly typedClient: Client<paths>;\n\n public constructor(options: OpenProjectClientOptions) {\n this.config = options.config;\n this.fetchImpl = options.fetchImpl ?? fetch;\n this.timeoutMs = options.timeoutMs ?? 30_000;\n this.typedClient = createClient<paths>({ baseUrl: `${this.config.baseUrl}/api/v3` });\n void this.typedClient;\n }\n\n public async getApiRoot(): Promise<unknown> {\n return this.request(\"GET\", \"/api/v3\");\n }\n\n public async getMe(): Promise<UserSummary> {\n return normalizeUser(await this.request(\"GET\", \"/api/v3/users/me\"));\n }\n\n public async listProjects(options: { readonly pageSize?: number } = {}): Promise<NormalizedCollection<ProjectSummary>> {\n const params = new URLSearchParams({ pageSize: String(normalizePageSize(options.pageSize)) });\n return normalizeCollection(await this.request(\"GET\", `/api/v3/projects?${params}`), normalizeProject);\n }\n\n public async getWorkPackageRaw(id: number): Promise<unknown> {\n return this.request(\"GET\", `/api/v3/work_packages/${encodeURIComponent(String(id))}`);\n }\n\n public async getWorkPackage(id: number): Promise<WorkPackageDetail> {\n return normalizeWorkPackageDetail(await this.getWorkPackageRaw(id));\n }\n\n public async searchWorkPackages(options: SearchWorkPackagesOptions): Promise<NormalizedCollection<WorkPackageSummary>> {\n const effectiveProject = options.project ?? this.config.defaultProject;\n const basePath = effectiveProject\n ? `/api/v3/projects/${encodeURIComponent(effectiveProject)}/work_packages`\n : \"/api/v3/work_packages\";\n const params = new URLSearchParams({ pageSize: String(normalizePageSize(options.pageSize)) });\n const filters = buildWorkPackageFilters(options);\n if (filters.length > 0) params.set(\"filters\", JSON.stringify(filters));\n return normalizeCollection(await this.request(\"GET\", `${basePath}?${params}`), normalizeWorkPackageSummary);\n }\n\n public async mine(options: Omit<SearchWorkPackagesOptions, \"assigneeMe\" | \"open\">): Promise<NormalizedCollection<WorkPackageSummary>> {\n await this.getMe();\n return this.searchWorkPackages({ ...options, assigneeMe: true, open: true });\n }\n\n public async commentWorkPackage(id: number, message: string, dryRun: boolean): Promise<CommentResult> {\n if (!this.config.allowWrite) throw new WriteBlockedError();\n if (message.trim() === \"\") throw new OpctlError(\"comment message must not be empty\", EXIT_CODES.validation);\n const raw = await this.getWorkPackageRaw(id);\n const detail = normalizeWorkPackageDetail(raw);\n const commentHref = findCommentHref(raw);\n if (!commentHref) {\n throw new OpctlError(\"commenting this work package is unsupported by the current OpenProject response/spec; no documented comment action link was found\", EXIT_CODES.validation);\n }\n const payload = { comment: { raw: message } };\n if (dryRun) {\n return { id, subject: detail.subject, status: \"dry-run\", request: { method: \"POST\", path: commentHref, payload } };\n }\n const response = await this.request(\"POST\", commentHref, payload);\n return { id, subject: detail.subject, status: \"comment posted\", link: getLink(response, \"self\")?.href ?? requireLinkHref(raw, \"self\") };\n }\n\n private async request(method: \"GET\" | \"POST\" | \"PATCH\", pathOrHref: string, body?: unknown): Promise<unknown> {\n const url = pathOrHref.startsWith(\"http://\") || pathOrHref.startsWith(\"https://\")\n ? pathOrHref\n : `${this.config.baseUrl}${pathOrHref.startsWith(\"/\") ? \"\" : \"/\"}${pathOrHref}`;\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), this.timeoutMs);\n try {\n const response = await this.fetchImpl(url, {\n method,\n signal: controller.signal,\n headers: {\n Accept: \"application/hal+json, application/json\",\n ...(body === undefined ? {} : { \"Content-Type\": \"application/json\" }),\n Authorization: createAuthorizationHeader(this.config.authMode, this.config.token),\n },\n ...(body === undefined ? {} : { body: JSON.stringify(body) }),\n });\n const parsed = await parseResponse(response);\n if (!response.ok) throw new OpenProjectHttpError(response.status, parsed);\n return parsed;\n } catch (error) {\n if (error instanceof OpenProjectHttpError || error instanceof OpctlError) throw error;\n if (error instanceof Error && error.name === \"AbortError\") throw new NetworkError(\"OpenProject request timed out\");\n throw new NetworkError(error instanceof Error ? error.message : \"OpenProject network request failed\");\n } finally {\n clearTimeout(timeout);\n }\n }\n}\n\nexport function buildWorkPackageFilters(options: Pick<SearchWorkPackagesOptions, \"subject\" | \"assigneeMe\" | \"status\" | \"open\">): unknown[] {\n const filters: unknown[] = [];\n if (options.subject && options.subject.trim() !== \"\") filters.push({ subject: { operator: \"~\", values: [options.subject] } });\n if (options.assigneeMe) filters.push({ assignee: { operator: \"=\", values: [\"me\"] } });\n if (options.open) filters.push({ status: { operator: \"o\", values: [] } });\n if (options.status && options.status.trim() !== \"\") {\n if (options.status === \"open\") filters.push({ status: { operator: \"o\", values: [] } });\n else filters.push({ status: { operator: \"=\", values: [options.status] } });\n }\n return filters;\n}\n\nfunction findCommentHref(resource: unknown): string | undefined {\n for (const name of [\"addComment\", \"addCommentImmediately\", \"comment\", \"addWorkPackageComment\"]) {\n const link = getLink(resource, name);\n if (link?.href && (!link.method || link.method.toUpperCase() === \"POST\")) return link.href;\n }\n return undefined;\n}\n\nasync function parseResponse(response: Response): Promise<unknown> {\n if (response.status === 204) return undefined;\n const text = await response.text();\n if (text.trim() === \"\") return undefined;\n const contentType = response.headers.get(\"content-type\") ?? \"\";\n if (contentType.includes(\"json\") || contentType.includes(\"hal\")) {\n try {\n return JSON.parse(text);\n } catch {\n return { message: \"OpenProject returned invalid JSON\" };\n }\n }\n return { message: text };\n}\n","import { ConfigurationError } from \"./client/errors.js\";\n\nexport type AuthMode = \"bearer\" | \"basic\";\n\nexport interface OpctlConfig {\n readonly baseUrl: string;\n readonly token: string;\n readonly authMode: AuthMode;\n readonly allowWrite: boolean;\n readonly defaultProject?: string;\n}\n\nexport interface EnvReader {\n readonly OPENPROJECT_URL?: string;\n readonly OPENPROJECT_TOKEN?: string;\n readonly OPENPROJECT_AUTH_MODE?: string;\n readonly OPENPROJECT_ALLOW_WRITE?: string;\n readonly OPENPROJECT_DEFAULT_PROJECT?: string;\n}\n\nexport function loadConfig(env: EnvReader = process.env): OpctlConfig {\n const rawUrl = env.OPENPROJECT_URL;\n const rawToken = env.OPENPROJECT_TOKEN;\n if (!rawUrl || rawUrl.trim() === \"\") throw new ConfigurationError(\"OPENPROJECT_URL is required\");\n if (!rawToken || rawToken.trim() === \"\") throw new ConfigurationError(\"OPENPROJECT_TOKEN is required\");\n\n const authMode = parseAuthMode(env.OPENPROJECT_AUTH_MODE);\n const defaultProject = cleanOptional(env.OPENPROJECT_DEFAULT_PROJECT);\n return {\n baseUrl: normalizeBaseUrl(rawUrl),\n token: rawToken,\n authMode,\n allowWrite: env.OPENPROJECT_ALLOW_WRITE === \"1\",\n ...(defaultProject ? { defaultProject } : {}),\n };\n}\n\nexport function normalizeBaseUrl(rawUrl: string): string {\n let parsed: URL;\n try {\n parsed = new URL(rawUrl.trim());\n } catch {\n throw new ConfigurationError(\"OPENPROJECT_URL must be an absolute URL\");\n }\n if (parsed.protocol !== \"https:\" && parsed.protocol !== \"http:\") {\n throw new ConfigurationError(\"OPENPROJECT_URL must use http or https\");\n }\n parsed.hash = \"\";\n parsed.search = \"\";\n const withoutTrailing = parsed.toString().replace(/\\/+$/, \"\");\n return withoutTrailing;\n}\n\nfunction parseAuthMode(raw: string | undefined): AuthMode {\n if (!raw || raw.trim() === \"\") return \"bearer\";\n const normalized = raw.trim().toLowerCase();\n if (normalized === \"bearer\" || normalized === \"basic\") return normalized;\n throw new ConfigurationError(\"OPENPROJECT_AUTH_MODE must be bearer or basic\");\n}\n\nfunction cleanOptional(raw: string | undefined): string | undefined {\n if (!raw) return undefined;\n const value = raw.trim();\n return value === \"\" ? undefined : value;\n}\n","import type { Command } from \"commander\";\nimport { OpenProjectClient } from \"../client/openProjectClient.js\";\nimport { loadConfig } from \"../config.js\";\nimport { stableJson } from \"../output/json.js\";\n\nexport interface CommandContext {\n readonly stdout: Pick<NodeJS.WriteStream, \"write\">;\n readonly stderr: Pick<NodeJS.WriteStream, \"write\">;\n readonly env: NodeJS.ProcessEnv;\n readonly fetchImpl?: typeof fetch;\n}\n\nexport function createClient(context: CommandContext): OpenProjectClient {\n return new OpenProjectClient({\n config: loadConfig(context.env),\n ...(context.fetchImpl ? { fetchImpl: context.fetchImpl } : {}),\n });\n}\n\nexport function writeOutput(context: CommandContext, value: unknown, json: boolean, renderText: () => string): void {\n context.stdout.write(json ? stableJson(value, context.env.OPENPROJECT_TOKEN) : renderText());\n}\n\nexport function booleanOption(command: Command, name: string): boolean {\n return Boolean(command.opts<Record<string, unknown>>()[name]);\n}\n","import type { Command } from \"commander\";\nimport { asObject } from \"../client/hal.js\";\nimport { stableJson } from \"../output/json.js\";\nimport { renderKeyValue } from \"../output/text.js\";\nimport { createClient, type CommandContext } from \"./context.js\";\n\nexport function registerApiRoot(program: Command, context: CommandContext): void {\n program\n .command(\"api-root\")\n .description(\"Show compact OpenProject API root links\")\n .option(\"--json\", \"emit JSON\")\n .action(async (options: { json?: boolean }) => {\n const root = await createClient(context).getApiRoot();\n const links = compactLinks(root);\n context.stdout.write(options.json ? stableJson(links, context.env.OPENPROJECT_TOKEN) : renderKeyValue(links));\n });\n}\n\nexport function compactLinks(root: unknown): Record<string, string> {\n const links = asObject(asObject(root)?._links) ?? {};\n const output: Record<string, string> = {};\n for (const [name, raw] of Object.entries(links)) {\n const link = Array.isArray(raw) ? asObject(raw[0]) : asObject(raw);\n if (typeof link?.href === \"string\") output[name] = link.href;\n }\n return output;\n}\n","import type { Command } from \"commander\";\nimport { createClient, type CommandContext, writeOutput } from \"./context.js\";\nimport { renderKeyValue } from \"../output/text.js\";\n\nexport function registerMe(program: Command, context: CommandContext): void {\n program\n .command(\"me\")\n .description(\"Show the authenticated OpenProject user\")\n .option(\"--json\", \"emit JSON\")\n .action(async (options: { json?: boolean }) => {\n const me = await createClient(context).getMe();\n writeOutput(context, me, Boolean(options.json), () => renderKeyValue(me as unknown as Record<string, unknown>));\n });\n}\n","export function renderTable(rows: readonly object[], columns: readonly string[]): string {\n const widths = columns.map((column) => Math.max(column.length, ...rows.map((row) => cell((row as Record<string, unknown>)[column]).length)));\n const header = columns.map((column, index) => column.padEnd(widths[index] ?? column.length)).join(\" \");\n const divider = widths.map((width) => \"-\".repeat(width)).join(\" \");\n const body = rows.map((row) => columns.map((column, index) => cell((row as Record<string, unknown>)[column]).padEnd(widths[index] ?? 0)).join(\" \"));\n return `${[header, divider, ...body].join(\"\\n\")}\\n`;\n}\n\nfunction cell(value: unknown): string {\n if (value === undefined || value === null) return \"\";\n return String(value);\n}\n","import type { Command } from \"commander\";\nimport { renderTable } from \"../output/table.js\";\nimport { createClient, type CommandContext, writeOutput } from \"./context.js\";\n\nexport function registerProjects(program: Command, context: CommandContext): void {\n program\n .command(\"projects\")\n .description(\"List visible OpenProject projects\")\n .option(\"--json\", \"emit JSON\")\n .option(\"--page-size <n>\", \"page size\", Number)\n .action(async (options: { json?: boolean; pageSize?: number }) => {\n const projects = await createClient(context).listProjects(options.pageSize === undefined ? {} : { pageSize: options.pageSize });\n writeOutput(context, projects, Boolean(options.json), () => renderTable(projects.elements, [\"id\", \"identifier\", \"name\", \"href\"]));\n });\n}\n","import { mkdir, writeFile } from \"node:fs/promises\";\nimport { dirname } from \"node:path\";\nimport { createAuthorizationHeader, redactSecrets } from \"../src/client/auth.js\";\nimport { OpenApiGenerationError } from \"../src/client/errors.js\";\nimport { normalizeBaseUrl, type AuthMode } from \"../src/config.js\";\n\nexport interface PullSpecOptions {\n readonly env?: NodeJS.ProcessEnv;\n readonly fetchImpl?: typeof fetch;\n readonly outputPath?: string;\n readonly timeoutMs?: number;\n readonly stdout?: Pick<typeof process.stdout, \"write\">;\n}\n\nexport interface PullSpecResult {\n readonly sourceHost: string;\n readonly outputPath: string;\n readonly title: string;\n readonly version: string;\n}\n\nexport async function pullOpenApiSpec(options: PullSpecOptions = {}): Promise<PullSpecResult> {\n const env = options.env ?? process.env;\n const outputPath = options.outputPath ?? \"openapi/openproject.json\";\n const fetchImpl = options.fetchImpl ?? fetch;\n const rawUrl = env.OPENPROJECT_URL;\n if (!rawUrl || rawUrl.trim() === \"\") throw new OpenApiGenerationError(\"OPENPROJECT_URL is required to pull the OpenProject spec\");\n const baseUrl = normalizeBaseUrl(rawUrl);\n const authMode = parseAuthMode(env.OPENPROJECT_AUTH_MODE);\n const headers: Record<string, string> = { Accept: \"application/json\" };\n if (env.OPENPROJECT_TOKEN && env.OPENPROJECT_TOKEN.trim() !== \"\") {\n headers.Authorization = createAuthorizationHeader(authMode, env.OPENPROJECT_TOKEN);\n }\n\n const specUrl = `${baseUrl}/api/v3/spec.json`;\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), options.timeoutMs ?? 15_000);\n let response: Response;\n try {\n response = await fetchImpl(specUrl, { headers, signal: controller.signal });\n } catch (error) {\n throw new OpenApiGenerationError(`failed to download OpenProject spec from ${new URL(baseUrl).host}: ${error instanceof Error ? redactSecrets(error.message, env.OPENPROJECT_TOKEN) : \"network error\"}`);\n } finally {\n clearTimeout(timeout);\n }\n\n if (!response.ok) {\n throw new OpenApiGenerationError(`failed to download OpenProject spec from ${new URL(baseUrl).host}: HTTP ${response.status}`);\n }\n\n const text = await response.text();\n let spec: unknown;\n try {\n spec = JSON.parse(text);\n } catch {\n throw new OpenApiGenerationError(\"OpenProject spec response was not valid JSON; spec.yml is not supported by this downloader\");\n }\n if (!spec || typeof spec !== \"object\") throw new OpenApiGenerationError(\"OpenProject spec response was not an object\");\n const info = \"info\" in spec && typeof spec.info === \"object\" && spec.info !== null ? spec.info : undefined;\n const title = info && \"title\" in info && typeof info.title === \"string\" ? info.title : \"OpenProject API\";\n const version = info && \"version\" in info && typeof info.version === \"string\" ? info.version : \"unknown\";\n\n await mkdir(dirname(outputPath), { recursive: true });\n await writeFile(outputPath, `${JSON.stringify(spec, null, 2)}\\n`, \"utf8\");\n const result = { sourceHost: new URL(baseUrl).host, outputPath, title, version };\n options.stdout?.write(`Downloaded OpenProject spec from ${result.sourceHost} to ${result.outputPath} (${result.title} ${result.version})\\n`);\n return result;\n}\n\nfunction parseAuthMode(raw: string | undefined): AuthMode {\n const normalized = raw?.trim().toLowerCase() ?? \"\";\n if (normalized === \"\" || normalized === \"bearer\") return \"bearer\";\n if (normalized === \"basic\") return \"basic\";\n throw new OpenApiGenerationError(\"OPENPROJECT_AUTH_MODE must be bearer or basic\");\n}\n\nif (import.meta.url === `file://${process.argv[1]}`) {\n pullOpenApiSpec({ stdout: process.stdout }).catch((error: unknown) => {\n const message = error instanceof Error ? error.message : \"failed to pull OpenProject spec\";\n process.stderr.write(`${redactSecrets(message, process.env.OPENPROJECT_TOKEN)}\\n`);\n process.exitCode = 8;\n });\n}\n","import type { Command } from \"commander\";\nimport { pullOpenApiSpec } from \"../../scripts/pull-openapi-spec.js\";\nimport type { CommandContext } from \"./context.js\";\n\nexport function registerSpec(program: Command, context: CommandContext): void {\n const spec = program.command(\"spec\").description(\"OpenAPI spec utilities\");\n spec.command(\"pull\")\n .description(\"Download OpenProject /api/v3/spec.json safely\")\n .action(async () => {\n await pullOpenApiSpec({ env: context.env, stdout: context.stdout, ...(context.fetchImpl ? { fetchImpl: context.fetchImpl } : {}) });\n });\n}\n","import type { Command } from \"commander\";\nimport { OpctlError, EXIT_CODES } from \"../client/errors.js\";\nimport { stableJson } from \"../output/json.js\";\nimport { renderKeyValue } from \"../output/text.js\";\nimport { renderTable } from \"../output/table.js\";\nimport { createClient, type CommandContext, writeOutput } from \"./context.js\";\n\ninterface SearchOptions {\n readonly json?: boolean;\n readonly project?: string;\n readonly subject?: string;\n readonly assigneeMe?: boolean;\n readonly status?: string;\n readonly pageSize?: number;\n}\n\nexport function registerWorkPackages(program: Command, context: CommandContext): void {\n const wp = program.command(\"wp\").description(\"Work package commands\");\n\n wp.command(\"get\")\n .description(\"Get one work package\")\n .argument(\"<id>\", \"work package id\")\n .option(\"--json\", \"emit normalized JSON\")\n .option(\"--raw-json\", \"emit raw OpenProject JSON\")\n .action(async (id: string, options: { json?: boolean; rawJson?: boolean }) => {\n const numericId = parseId(id);\n const client = createClient(context);\n if (options.rawJson) {\n context.stdout.write(stableJson(await client.getWorkPackageRaw(numericId), context.env.OPENPROJECT_TOKEN));\n return;\n }\n const workPackage = await client.getWorkPackage(numericId);\n writeOutput(context, workPackage, Boolean(options.json), () => renderKeyValue(workPackage as unknown as Record<string, unknown>));\n });\n\n wp.command(\"search\")\n .description(\"Search work packages\")\n .option(\"--json\", \"emit JSON\")\n .option(\"--project <identifier-or-id>\", \"project identifier or id\")\n .option(\"--subject <text>\", \"subject contains text\")\n .option(\"--assignee-me\", \"filter to current user\")\n .option(\"--status <id-or-open>\", \"status id, or open\")\n .option(\"--page-size <n>\", \"page size\", Number)\n .action(async (options: SearchOptions) => {\n const result = await createClient(context).searchWorkPackages(options);\n writeOutput(context, result, Boolean(options.json), () => renderTable(result.elements, [\"id\", \"subject\", \"status\", \"assignee\", \"project\", \"href\"]));\n });\n\n wp.command(\"mine\")\n .description(\"List open work packages assigned to the authenticated user\")\n .option(\"--json\", \"emit JSON\")\n .option(\"--project <identifier-or-id>\", \"project identifier or id\")\n .option(\"--page-size <n>\", \"page size\", Number)\n .action(async (options: Pick<SearchOptions, \"json\" | \"project\" | \"pageSize\">) => {\n const result = await createClient(context).mine(options);\n writeOutput(context, result, Boolean(options.json), () => renderTable(result.elements, [\"id\", \"subject\", \"status\", \"assignee\", \"project\", \"href\"]));\n });\n\n wp.command(\"comment\")\n .description(\"Add a comment to a work package; requires OPENPROJECT_ALLOW_WRITE=1\")\n .argument(\"<id>\", \"work package id\")\n .argument(\"<message>\", \"comment message\")\n .option(\"--dry-run\", \"print intended mutation without posting\")\n .option(\"--json\", \"emit JSON\")\n .action(async (id: string, message: string, options: { dryRun?: boolean; json?: boolean }) => {\n const result = await createClient(context).commentWorkPackage(parseId(id), message, Boolean(options.dryRun));\n writeOutput(context, result, Boolean(options.json), () => renderKeyValue(result as unknown as Record<string, unknown>));\n });\n}\n\nfunction parseId(id: string): number {\n const parsed = Number(id);\n if (!Number.isInteger(parsed) || parsed < 1) throw new OpctlError(\"work package id must be a positive integer\", EXIT_CODES.validation);\n return parsed;\n}\n","{\n \"name\": \"opctl\",\n \"version\": \"0.1.2\",\n \"description\": \"Conservative local CLI bridge for OpenProject API v3\",\n \"type\": \"module\",\n \"bin\": {\n \"opctl\": \"./dist/cli.js\"\n },\n \"files\": [\n \"dist\"\n ],\n \"scripts\": {\n \"dev\": \"tsx src/cli.ts\",\n \"build\": \"npm run typecheck && vite build\",\n \"typecheck\": \"tsc --noEmit\",\n \"test\": \"vitest run\",\n \"openapi:pull\": \"tsx scripts/pull-openapi-spec.ts\",\n \"openapi:generate\": \"tsx scripts/generate-openapi-types.ts\",\n \"openapi:update\": \"npm run openapi:pull && npm run openapi:generate\"\n },\n \"keywords\": [\n \"openproject\",\n \"cli\"\n ],\n \"author\": \"\",\n \"license\": \"ISC\",\n \"dependencies\": {\n \"commander\": \"latest\",\n \"openapi-fetch\": \"latest\"\n },\n \"devDependencies\": {\n \"@types/node\": \"latest\",\n \"openapi-typescript\": \"latest\",\n \"tsx\": \"latest\",\n \"typescript\": \"latest\",\n \"vite\": \"latest\",\n \"vitest\": \"latest\",\n \"yaml\": \"^2.9.0\"\n },\n \"packageManager\": \"pnpm@11.1.3\"\n}\n","#!/usr/bin/env node\nimport { realpathSync } from \"node:fs\";\nimport { fileURLToPath } from \"node:url\";\nimport { Command } from \"commander\";\nimport { redactSecrets } from \"./client/auth.js\";\nimport { EXIT_CODES, toOpctlError } from \"./client/errors.js\";\nimport { stableJson } from \"./output/json.js\";\nimport { registerApiRoot } from \"./commands/apiRoot.js\";\nimport { registerMe } from \"./commands/me.js\";\nimport { registerProjects } from \"./commands/projects.js\";\nimport { registerSpec } from \"./commands/spec.js\";\nimport { registerWorkPackages } from \"./commands/workPackages.js\";\nimport type { CommandContext } from \"./commands/context.js\";\nimport pkg from \"../package.json\" with { type: \"json\" };\n\nexport function buildProgram(context: CommandContext): Command {\n const program = new Command();\n program\n .name(\"opctl\")\n .description(\"Conservative local CLI bridge for OpenProject API v3\")\n .version(pkg.version)\n .showHelpAfterError()\n .configureOutput({\n writeOut: (text) => context.stdout.write(text),\n writeErr: (text) => context.stderr.write(text),\n });\n registerMe(program, context);\n registerApiRoot(program, context);\n registerProjects(program, context);\n registerWorkPackages(program, context);\n registerSpec(program, context);\n return program;\n}\n\nexport async function run(argv: readonly string[], context: CommandContext): Promise<number> {\n try {\n await buildProgram(context).parseAsync(argv, { from: \"node\" });\n return EXIT_CODES.success;\n } catch (error) {\n const opctlError = toOpctlError(error);\n const wantsJson = argv.includes(\"--json\");\n if (wantsJson) context.stderr.write(stableJson({ error: opctlError.message, exitCode: opctlError.exitCode }, context.env.OPENPROJECT_TOKEN));\n else context.stderr.write(`${redactSecrets(opctlError.message, context.env.OPENPROJECT_TOKEN)}\\n`);\n return opctlError.exitCode;\n }\n}\n\nexport function isCliEntrypoint(metaUrl: string, argvPath: string | undefined = process.argv[1]): boolean {\n if (!argvPath) return false;\n try {\n return realpathSync(fileURLToPath(metaUrl)) === realpathSync(argvPath);\n } catch {\n return false;\n }\n}\n\nif (isCliEntrypoint(import.meta.url)) {\n const exitCode = await run(process.argv, { stdout: process.stdout, stderr: process.stderr, env: process.env });\n process.exitCode = exitCode;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"cli.js","names":[],"sources":["../src/client/auth.ts","../src/client/errors.ts","../src/output/json.ts","../src/client/hal.ts","../src/output/text.ts","../src/client/pagination.ts","../src/client/openProjectClient.ts","../src/config/envFile.ts","../src/config/profiles.ts","../src/config.ts","../src/commands/context.ts","../src/commands/apiRoot.ts","../src/commands/me.ts","../src/output/table.ts","../src/commands/projects.ts","../scripts/pull-openapi-spec.ts","../src/commands/spec.ts","../src/output/fields.ts","../src/commands/workPackages.ts","../src/commands/profile.ts","../package.json","../src/cli.ts"],"sourcesContent":["import type { AuthMode } from \"../config.js\";\n\nexport function createAuthorizationHeader(mode: AuthMode, token: string): string {\n if (mode === \"basic\") {\n return `Basic ${Buffer.from(`apikey:${token}`, \"utf8\").toString(\"base64\")}`;\n }\n return `Bearer ${token}`;\n}\n\nexport function redactSecrets(value: string, token?: string): string {\n let redacted = value.replace(/Authorization:\\s*(Bearer|Basic)\\s+[^\\s,}]+/gi, \"Authorization: <redacted>\");\n redacted = redacted.replace(/\"Authorization\"\\s*:\\s*\"[^\"]+\"/gi, '\"Authorization\":\"<redacted>\"');\n if (token && token !== \"\") redacted = redacted.split(token).join(\"<redacted>\");\n return redacted;\n}\n","export const EXIT_CODES = {\n success: 0,\n general: 1,\n config: 2,\n auth: 3,\n notFound: 4,\n validation: 5,\n writeBlocked: 6,\n network: 7,\n openapi: 8,\n} as const;\n\nexport type ExitCode = (typeof EXIT_CODES)[keyof typeof EXIT_CODES];\n\nexport class OpctlError extends Error {\n public readonly exitCode: ExitCode;\n public readonly details: unknown;\n\n public constructor(message: string, exitCode: ExitCode = EXIT_CODES.general, details?: unknown) {\n super(message);\n this.name = \"OpctlError\";\n this.exitCode = exitCode;\n this.details = details;\n }\n}\n\nexport class ConfigurationError extends OpctlError {\n public constructor(message: string) {\n super(message, EXIT_CODES.config);\n this.name = \"ConfigurationError\";\n }\n}\n\nexport class WriteBlockedError extends OpctlError {\n public constructor() {\n super(\"OpenProject write blocked: set OPENPROJECT_ALLOW_WRITE=1 to enable write commands\", EXIT_CODES.writeBlocked);\n this.name = \"WriteBlockedError\";\n }\n}\n\nexport class NetworkError extends OpctlError {\n public constructor(message: string) {\n super(message, EXIT_CODES.network);\n this.name = \"NetworkError\";\n }\n}\n\nexport class OpenApiGenerationError extends OpctlError {\n public constructor(message: string) {\n super(message, EXIT_CODES.openapi);\n this.name = \"OpenApiGenerationError\";\n }\n}\n\nexport class OpenProjectHttpError extends OpctlError {\n public readonly status: number;\n public readonly responseBody: unknown;\n\n public constructor(status: number, responseBody: unknown) {\n super(httpStatusMessage(status, responseBody), exitCodeForStatus(status), responseBody);\n this.name = \"OpenProjectHttpError\";\n this.status = status;\n this.responseBody = responseBody;\n }\n}\n\nexport function exitCodeForStatus(status: number): ExitCode {\n if (status === 401 || status === 403) return EXIT_CODES.auth;\n if (status === 404) return EXIT_CODES.notFound;\n if (status === 422) return EXIT_CODES.validation;\n return EXIT_CODES.general;\n}\n\nexport function httpStatusMessage(status: number, body: unknown): string {\n if (status === 401) return \"authentication failed\";\n if (status === 403) return \"authenticated OpenProject user lacks permission\";\n if (status === 404) return \"resource not found or not visible to this user\";\n if (status === 409) return \"possible stale lockVersion or concurrent modification\";\n if (status === 422) return `validation failed${validationDetail(body)}`;\n return `OpenProject request failed with HTTP ${status}`;\n}\n\nfunction validationDetail(body: unknown): string {\n if (!body || typeof body !== \"object\") return \"\";\n const message = \"message\" in body && typeof body.message === \"string\" ? body.message : undefined;\n const errorIdentifier = \"errorIdentifier\" in body && typeof body.errorIdentifier === \"string\" ? body.errorIdentifier : undefined;\n const errors = \"_embedded\" in body ? body._embedded : undefined;\n const parts = [message, errorIdentifier, typeof errors === \"object\" && errors !== null ? JSON.stringify(errors) : undefined].filter(Boolean);\n return parts.length === 0 ? \"\" : `: ${parts.join(\"; \")}`;\n}\n\nexport function toOpctlError(error: unknown): OpctlError {\n if (error instanceof OpctlError) return error;\n if (error instanceof Error) return new OpctlError(error.message);\n return new OpctlError(\"unexpected failure\");\n}\n","import { redactSecrets } from \"../client/auth.js\";\n\nexport function stableJson(value: unknown, token?: string): string {\n return `${redactSecrets(JSON.stringify(sortForJson(value), null, 2), token)}\\n`;\n}\n\nfunction sortForJson(value: unknown): unknown {\n if (Array.isArray(value)) return value.map(sortForJson);\n if (!value || typeof value !== \"object\") return value;\n const sorted: Record<string, unknown> = {};\n for (const key of Object.keys(value).sort()) sorted[key] = sortForJson((value as Record<string, unknown>)[key]);\n return sorted;\n}\n","import type { LinkSummary, ProjectSummary, UserSummary, WorkPackageDetail, WorkPackageSummary } from \"../types/domain.js\";\n\ntype HalObject = Record<string, unknown>;\n\nexport function asObject(value: unknown): HalObject | undefined {\n return value && typeof value === \"object\" ? (value as HalObject) : undefined;\n}\n\nexport function getLink(resource: unknown, name: string): LinkSummary | undefined {\n const object = asObject(resource);\n const links = asObject(object?._links);\n const raw = links?.[name];\n const link = Array.isArray(raw) ? asObject(raw[0]) : asObject(raw);\n if (!link) return undefined;\n const href = typeof link.href === \"string\" ? link.href : undefined;\n const title = typeof link.title === \"string\" ? link.title : undefined;\n const method = typeof link.method === \"string\" ? link.method : undefined;\n if (!href && !title && !method) return undefined;\n return { ...(href ? { href } : {}), ...(title ? { title } : {}), ...(method ? { method } : {}) };\n}\n\nexport function requireLinkHref(resource: unknown, name: string): string | undefined {\n return getLink(resource, name)?.href;\n}\n\nexport function collectionElements(resource: unknown): unknown[] {\n const embedded = asObject(asObject(resource)?._embedded);\n const elements = embedded?.elements;\n return Array.isArray(elements) ? elements : [];\n}\n\nexport function collectionTotal(resource: unknown): number | undefined {\n const total = asObject(resource)?.total;\n return typeof total === \"number\" ? total : undefined;\n}\n\nexport function normalizeUser(resource: unknown): UserSummary {\n const object = asObject(resource) ?? {};\n return {\n id: numberField(object.id),\n name: stringField(object.name),\n login: stringField(object.login),\n email: stringField(object.email),\n href: getLink(object, \"self\")?.href,\n };\n}\n\nexport function normalizeProject(resource: unknown): ProjectSummary {\n const object = asObject(resource) ?? {};\n return {\n id: numberField(object.id),\n identifier: stringField(object.identifier),\n name: stringField(object.name),\n href: getLink(object, \"self\")?.href,\n };\n}\n\nexport function normalizeWorkPackageSummary(resource: unknown): WorkPackageSummary {\n const object = asObject(resource) ?? {};\n return {\n id: numberField(object.id),\n subject: stringField(object.subject),\n status: getLink(object, \"status\")?.title,\n assignee: getLink(object, \"assignee\")?.title,\n project: getLink(object, \"project\")?.title,\n type: getLink(object, \"type\")?.title,\n href: getLink(object, \"self\")?.href,\n updatedAt: stringField(object.updatedAt),\n shortDescription: shortDescription(extractDescription(object.description)),\n attachmentsCount: attachmentsCount(object),\n };\n}\n\nexport function normalizeWorkPackageDetail(resource: unknown): WorkPackageDetail {\n const object = asObject(resource) ?? {};\n return {\n ...normalizeWorkPackageSummary(object),\n description: extractDescription(object.description),\n shortDescription: shortDescription(extractDescription(object.description)),\n attachmentsCount: attachmentsCount(object),\n lockVersion: numberField(object.lockVersion),\n actions: actionLinks(object),\n };\n}\n\nexport function actionLinks(resource: unknown): Record<string, LinkSummary> {\n const links = asObject(asObject(resource)?._links) ?? {};\n const actions: Record<string, LinkSummary> = {};\n for (const [name, value] of Object.entries(links)) {\n const link = Array.isArray(value) ? asObject(value[0]) : asObject(value);\n if (!link) continue;\n const method = typeof link.method === \"string\" ? link.method : undefined;\n if (!method && !name.startsWith(\"add\") && !name.includes(\"update\") && !name.includes(\"delete\")) continue;\n const summary = getLink(resource, name);\n if (summary) actions[name] = summary;\n }\n return actions;\n}\n\nexport function extractDescription(value: unknown): string | undefined {\n if (typeof value === \"string\") return value;\n const object = asObject(value);\n const raw = typeof object?.raw === \"string\" ? object.raw : undefined;\n const html = typeof object?.html === \"string\" ? object.html : undefined;\n return raw ?? (html ? html.replace(/<[^>]+>/g, \" \").replace(/\\s+/g, \" \").trim() : undefined);\n}\n\nfunction shortDescription(description: string | undefined): string | undefined {\n if (!description) return undefined;\n const compact = description.replace(/\\s+/g, \" \").trim();\n if (compact === \"\") return undefined;\n return compact.length <= 160 ? compact : `${compact.slice(0, 157)}...`;\n}\n\nfunction attachmentsCount(resource: HalObject): number | undefined {\n const embeddedAttachments = asObject(asObject(resource._embedded)?.attachments);\n const total = numberField(embeddedAttachments?.total) ?? numberField(embeddedAttachments?.count);\n if (total !== undefined) return total;\n const elements = embeddedAttachments?.elements;\n if (Array.isArray(elements)) return elements.length;\n const direct = asObject(resource.attachments);\n const directTotal = numberField(direct?.total) ?? numberField(direct?.count);\n if (directTotal !== undefined) return directTotal;\n const directElements = direct?.elements;\n if (Array.isArray(directElements)) return directElements.length;\n return undefined;\n}\n\nfunction stringField(value: unknown): string | undefined {\n return typeof value === \"string\" ? value : undefined;\n}\n\nfunction numberField(value: unknown): number | undefined {\n return typeof value === \"number\" ? value : undefined;\n}\n","export function renderKeyValue(value: Record<string, unknown>): string {\n return `${Object.entries(value)\n .filter(([, item]) => item !== undefined)\n .map(([key, item]) => `${key}: ${String(item)}`)\n .join(\"\\n\")}\\n`;\n}\n","import { collectionElements, collectionTotal } from \"./hal.js\";\n\nexport interface PageOptions {\n readonly pageSize?: number;\n}\n\nexport interface NormalizedCollection<T> {\n readonly elements: readonly T[];\n readonly total?: number;\n readonly count: number;\n}\n\nexport function normalizePageSize(value: unknown, fallback = 25): number {\n if (value === undefined || value === null || value === \"\") return fallback;\n const parsed = typeof value === \"number\" ? value : Number(value);\n if (!Number.isInteger(parsed) || parsed < 1 || parsed > 100) throw new Error(\"page size must be an integer between 1 and 100\");\n return parsed;\n}\n\nexport function normalizeCollection<T>(resource: unknown, mapper: (value: unknown) => T): NormalizedCollection<T> {\n const elements = collectionElements(resource).map(mapper);\n const total = collectionTotal(resource);\n return { elements, ...(total === undefined ? {} : { total }), count: elements.length };\n}\n","import createClient, { type Client } from \"openapi-fetch\";\nimport { createAuthorizationHeader } from \"./auth.js\";\nimport { OpenProjectHttpError, NetworkError, WriteBlockedError, OpctlError, EXIT_CODES } from \"./errors.js\";\nimport { normalizeCollection, normalizePageSize, type NormalizedCollection } from \"./pagination.js\";\nimport { getLink, normalizeProject, normalizeUser, normalizeWorkPackageDetail, normalizeWorkPackageSummary, requireLinkHref } from \"./hal.js\";\nimport type { OpctlConfig } from \"../config.js\";\nimport type { paths } from \"../generated/openproject.js\";\nimport type { CommentResult, ProjectSummary, UserSummary, WorkPackageDetail, WorkPackageSummary } from \"../types/domain.js\";\n\nexport interface SearchWorkPackagesOptions {\n readonly project?: string;\n readonly subject?: string;\n readonly assigneeMe?: boolean;\n readonly status?: string;\n readonly open?: boolean;\n readonly pageSize?: number;\n}\n\nexport interface OpenProjectClientOptions {\n readonly config: OpctlConfig;\n readonly fetchImpl?: typeof fetch;\n readonly timeoutMs?: number;\n}\n\nexport class OpenProjectClient {\n private readonly config: OpctlConfig;\n private readonly fetchImpl: typeof fetch;\n private readonly timeoutMs: number;\n private readonly typedClient: Client<paths>;\n\n public constructor(options: OpenProjectClientOptions) {\n this.config = options.config;\n this.fetchImpl = options.fetchImpl ?? fetch;\n this.timeoutMs = options.timeoutMs ?? 30_000;\n this.typedClient = createClient<paths>({ baseUrl: `${this.config.baseUrl}/api/v3` });\n void this.typedClient;\n }\n\n public async getApiRoot(): Promise<unknown> {\n return this.request(\"GET\", \"/api/v3\");\n }\n\n public async getMe(): Promise<UserSummary> {\n return normalizeUser(await this.request(\"GET\", \"/api/v3/users/me\"));\n }\n\n public async listProjects(options: { readonly pageSize?: number } = {}): Promise<NormalizedCollection<ProjectSummary>> {\n const params = new URLSearchParams({ pageSize: String(normalizePageSize(options.pageSize)) });\n return normalizeCollection(await this.request(\"GET\", `/api/v3/projects?${params}`), normalizeProject);\n }\n\n public async getWorkPackageRaw(id: number): Promise<unknown> {\n return this.request(\"GET\", `/api/v3/work_packages/${encodeURIComponent(String(id))}`);\n }\n\n public async getWorkPackage(id: number): Promise<WorkPackageDetail> {\n return normalizeWorkPackageDetail(await this.getWorkPackageRaw(id));\n }\n\n public async searchWorkPackages(options: SearchWorkPackagesOptions): Promise<NormalizedCollection<WorkPackageSummary>> {\n const effectiveProject = options.project ?? this.config.defaultProject;\n const basePath = effectiveProject\n ? `/api/v3/projects/${encodeURIComponent(effectiveProject)}/work_packages`\n : \"/api/v3/work_packages\";\n const params = new URLSearchParams({ pageSize: String(normalizePageSize(options.pageSize)) });\n const filters = buildWorkPackageFilters(options);\n if (filters.length > 0) params.set(\"filters\", JSON.stringify(filters));\n return normalizeCollection(await this.request(\"GET\", `${basePath}?${params}`), normalizeWorkPackageSummary);\n }\n\n public async mine(options: Omit<SearchWorkPackagesOptions, \"assigneeMe\" | \"open\">): Promise<NormalizedCollection<WorkPackageSummary>> {\n await this.getMe();\n return this.searchWorkPackages({ ...options, assigneeMe: true, open: true });\n }\n\n public async commentWorkPackage(id: number, message: string, dryRun: boolean): Promise<CommentResult> {\n if (!this.config.allowWrite) throw new WriteBlockedError();\n if (message.trim() === \"\") throw new OpctlError(\"comment message must not be empty\", EXIT_CODES.validation);\n const raw = await this.getWorkPackageRaw(id);\n const detail = normalizeWorkPackageDetail(raw);\n const commentHref = findCommentHref(raw);\n if (!commentHref) {\n throw new OpctlError(\"commenting this work package is unsupported by the current OpenProject response/spec; no documented comment action link was found\", EXIT_CODES.validation);\n }\n const payload = { comment: { raw: message } };\n if (dryRun) {\n return { id, subject: detail.subject, status: \"dry-run\", request: { method: \"POST\", path: commentHref, payload } };\n }\n const response = await this.request(\"POST\", commentHref, payload);\n return { id, subject: detail.subject, status: \"comment posted\", link: getLink(response, \"self\")?.href ?? requireLinkHref(raw, \"self\") };\n }\n\n private async request(method: \"GET\" | \"POST\" | \"PATCH\", pathOrHref: string, body?: unknown): Promise<unknown> {\n const url = pathOrHref.startsWith(\"http://\") || pathOrHref.startsWith(\"https://\")\n ? pathOrHref\n : `${this.config.baseUrl}${pathOrHref.startsWith(\"/\") ? \"\" : \"/\"}${pathOrHref}`;\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), this.timeoutMs);\n try {\n const response = await this.fetchImpl(url, {\n method,\n signal: controller.signal,\n headers: {\n Accept: \"application/hal+json, application/json\",\n ...(body === undefined ? {} : { \"Content-Type\": \"application/json\" }),\n Authorization: createAuthorizationHeader(this.config.authMode, this.config.token),\n },\n ...(body === undefined ? {} : { body: JSON.stringify(body) }),\n });\n const parsed = await parseResponse(response);\n if (!response.ok) throw new OpenProjectHttpError(response.status, parsed);\n return parsed;\n } catch (error) {\n if (error instanceof OpenProjectHttpError || error instanceof OpctlError) throw error;\n if (error instanceof Error && error.name === \"AbortError\") throw new NetworkError(\"OpenProject request timed out\");\n throw new NetworkError(error instanceof Error ? error.message : \"OpenProject network request failed\");\n } finally {\n clearTimeout(timeout);\n }\n }\n}\n\nexport function buildWorkPackageFilters(options: Pick<SearchWorkPackagesOptions, \"subject\" | \"assigneeMe\" | \"status\" | \"open\">): unknown[] {\n const filters: unknown[] = [];\n if (options.subject && options.subject.trim() !== \"\") filters.push({ subject: { operator: \"~\", values: [options.subject] } });\n if (options.assigneeMe) filters.push({ assignee: { operator: \"=\", values: [\"me\"] } });\n const status = options.status?.trim();\n if (options.open || status === \"open\") filters.push({ status: { operator: \"o\", values: [] } });\n else if (status) filters.push({ status: { operator: \"=\", values: [status] } });\n return filters;\n}\n\nfunction findCommentHref(resource: unknown): string | undefined {\n for (const name of [\"addComment\", \"addCommentImmediately\", \"comment\", \"addWorkPackageComment\"]) {\n const link = getLink(resource, name);\n if (link?.href && (!link.method || link.method.toUpperCase() === \"POST\")) return link.href;\n }\n return undefined;\n}\n\nasync function parseResponse(response: Response): Promise<unknown> {\n if (response.status === 204) return undefined;\n const text = await response.text();\n if (text.trim() === \"\") return undefined;\n const contentType = response.headers.get(\"content-type\") ?? \"\";\n if (contentType.includes(\"json\") || contentType.includes(\"hal\")) {\n try {\n return JSON.parse(text);\n } catch {\n return { message: \"OpenProject returned invalid JSON\" };\n }\n }\n return { message: text };\n}\n","import { readFileSync } from \"node:fs\";\nimport { ConfigurationError } from \"../client/errors.js\";\nimport type { EnvReader } from \"../config.js\";\n\nconst ALLOWED_ENV_KEYS: Record<string, true> = {\n OPENPROJECT_AUTH_MODE: true,\n OPENPROJECT_DEFAULT_PROJECT: true,\n OPENPROJECT_TOKEN: true,\n OPENPROJECT_URL: true,\n};\n\nexport function loadEnvFile(path: string): EnvReader {\n return parseEnvFile(readFileSync(path, \"utf8\"));\n}\n\nexport function parseEnvFile(content: string): EnvReader {\n const env: Record<string, string> = {};\n const lines = content.split(/\\r?\\n/);\n for (let index = 0; index < lines.length; index += 1) {\n const parsed = parseLine(lines[index] ?? \"\", index + 1);\n if (!parsed) continue;\n if (ALLOWED_ENV_KEYS[parsed.key] !== true) continue;\n env[parsed.key] = parsed.value;\n }\n return env;\n}\n\nfunction parseLine(line: string, lineNumber: number): { readonly key: string; readonly value: string } | undefined {\n const trimmed = line.trim();\n if (trimmed === \"\" || trimmed.startsWith(\"#\")) return undefined;\n const equals = trimmed.indexOf(\"=\");\n if (equals <= 0) throw new ConfigurationError(`invalid .env line ${lineNumber}`);\n const key = trimmed.slice(0, equals).trim();\n if (!/^OPENPROJECT_[A-Z_]+$/.test(key)) return undefined;\n const rawValue = trimmed.slice(equals + 1).trim();\n return { key, value: unquoteValue(rawValue, lineNumber) };\n}\n\nfunction unquoteValue(value: string, lineNumber: number): string {\n if (value.startsWith('\"')) {\n if (!value.endsWith('\"') || value.length === 1) throw new ConfigurationError(`unterminated quoted value on .env line ${lineNumber}`);\n return value.slice(1, -1).replace(/\\\\n/g, \"\\n\").replace(/\\\\\"/g, '\"').replace(/\\\\\\\\/g, \"\\\\\");\n }\n if (value.startsWith(\"'\")) {\n if (!value.endsWith(\"'\") || value.length === 1) throw new ConfigurationError(`unterminated quoted value on .env line ${lineNumber}`);\n return value.slice(1, -1);\n }\n const hash = value.indexOf(\" #\");\n return (hash >= 0 ? value.slice(0, hash) : value).trim();\n}\n","import { existsSync, mkdirSync, readFileSync, writeFileSync, chmodSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { homedir } from \"node:os\";\nimport { ConfigurationError } from \"../client/errors.js\";\nimport type { EnvReader } from \"../config.js\";\n\nexport interface Profile {\n readonly url?: string;\n readonly authMode?: string;\n readonly defaultProject?: string;\n readonly token?: string;\n}\n\ninterface ProfileStore {\n readonly activeProfile?: string;\n readonly profiles: Record<string, Profile>;\n}\n\nconst EMPTY_STORE: ProfileStore = { profiles: {} };\n\nexport function profilesPath(env: NodeJS.ProcessEnv = process.env): string {\n const configHome = env.XDG_CONFIG_HOME && env.XDG_CONFIG_HOME.trim() !== \"\" ? env.XDG_CONFIG_HOME : join(homedir(), \".config\");\n return join(configHome, \"opctl\", \"profiles.json\");\n}\n\nexport function loadProfileStore(path = profilesPath()): ProfileStore {\n if (!existsSync(path)) return EMPTY_STORE;\n const parsed = JSON.parse(readFileSync(path, \"utf8\")) as unknown;\n if (!parsed || typeof parsed !== \"object\") throw new ConfigurationError(\"profile store is invalid\");\n const object = parsed as Record<string, unknown>;\n const profiles = object.profiles;\n if (!profiles || typeof profiles !== \"object\" || Array.isArray(profiles)) throw new ConfigurationError(\"profile store is invalid\");\n return {\n ...(typeof object.activeProfile === \"string\" ? { activeProfile: object.activeProfile } : {}),\n profiles: profiles as Record<string, Profile>,\n };\n}\n\nexport function saveProfileStore(store: ProfileStore, path = profilesPath()): void {\n mkdirSync(dirname(path), { recursive: true, mode: 0o700 });\n try { chmodSync(dirname(path), 0o700); } catch { /* best effort on platforms without chmod */ }\n writeFileSync(path, `${JSON.stringify(store, null, 2)}\\n`, { mode: 0o600 });\n try { chmodSync(path, 0o600); } catch { /* best effort on platforms without chmod */ }\n}\n\nexport function profileToEnv(profile: Profile | undefined): EnvReader {\n if (!profile) return {};\n return {\n ...(profile.url ? { OPENPROJECT_URL: profile.url } : {}),\n ...(profile.authMode ? { OPENPROJECT_AUTH_MODE: profile.authMode } : {}),\n ...(profile.defaultProject ? { OPENPROJECT_DEFAULT_PROJECT: profile.defaultProject } : {}),\n ...(profile.token ? { OPENPROJECT_TOKEN: profile.token } : {}),\n };\n}\n\nexport function getProfileEnv(name: string | undefined, env: NodeJS.ProcessEnv = process.env): EnvReader {\n const store = loadProfileStore(profilesPath(env));\n const selected = name ?? store.activeProfile;\n if (!selected) return {};\n const profile = store.profiles[selected];\n if (!profile) throw new ConfigurationError(`profile '${selected}' does not exist`);\n return profileToEnv(profile);\n}\n\nexport function listProfiles(env: NodeJS.ProcessEnv = process.env): ReadonlyArray<{ readonly name: string; readonly active: boolean; readonly profile: Profile }> {\n const store = loadProfileStore(profilesPath(env));\n return Object.entries(store.profiles).sort(([left], [right]) => left.localeCompare(right)).map(([name, profile]) => ({ name, active: name === store.activeProfile, profile }));\n}\n\nexport function setProfile(name: string, profile: Profile, env: NodeJS.ProcessEnv = process.env): void {\n validateProfileName(name);\n const path = profilesPath(env);\n const store = loadProfileStore(path);\n saveProfileStore({ ...store, profiles: { ...store.profiles, [name]: cleanProfile(profile) } }, path);\n}\n\nexport function useProfile(name: string, env: NodeJS.ProcessEnv = process.env): void {\n validateProfileName(name);\n const path = profilesPath(env);\n const store = loadProfileStore(path);\n if (!store.profiles[name]) throw new ConfigurationError(`profile '${name}' does not exist`);\n saveProfileStore({ ...store, activeProfile: name }, path);\n}\n\nexport function unsetProfile(name: string, env: NodeJS.ProcessEnv = process.env): void {\n validateProfileName(name);\n const path = profilesPath(env);\n const store = loadProfileStore(path);\n const { [name]: _removed, ...profiles } = store.profiles;\n saveProfileStore({ ...(store.activeProfile === name ? {} : { activeProfile: store.activeProfile }), profiles }, path);\n}\n\nexport function showProfile(name: string | undefined, env: NodeJS.ProcessEnv = process.env): { readonly name?: string; readonly profile?: Profile } {\n const store = loadProfileStore(profilesPath(env));\n const selected = name ?? store.activeProfile;\n if (!selected) return {};\n const profile = store.profiles[selected];\n if (!profile) throw new ConfigurationError(`profile '${selected}' does not exist`);\n return { name: selected, profile };\n}\n\nexport function redactProfile(profile: Profile): Profile & { readonly token?: string } {\n return { ...profile, ...(profile.token ? { token: \"<redacted>\" } : {}) };\n}\n\nfunction validateProfileName(name: string): void {\n if (!/^[A-Za-z0-9_.-]+$/.test(name)) throw new ConfigurationError(\"profile name may contain only letters, numbers, '.', '_', and '-'\");\n}\n\nfunction cleanProfile(profile: Profile): Profile {\n return {\n ...(profile.url && profile.url.trim() !== \"\" ? { url: profile.url.trim() } : {}),\n ...(profile.authMode && profile.authMode.trim() !== \"\" ? { authMode: profile.authMode.trim() } : {}),\n ...(profile.defaultProject && profile.defaultProject.trim() !== \"\" ? { defaultProject: profile.defaultProject.trim() } : {}),\n ...(profile.token && profile.token.trim() !== \"\" ? { token: profile.token } : {}),\n };\n}\n","import { ConfigurationError } from \"./client/errors.js\";\nimport { existsSync } from \"node:fs\";\nimport { join, resolve } from \"node:path\";\nimport { loadEnvFile } from \"./config/envFile.js\";\nimport { getProfileEnv } from \"./config/profiles.js\";\n\nexport type AuthMode = \"bearer\" | \"basic\";\n\nexport interface OpctlConfig {\n readonly baseUrl: string;\n readonly token: string;\n readonly authMode: AuthMode;\n readonly allowWrite: boolean;\n readonly defaultProject?: string;\n}\n\nexport interface EnvReader {\n readonly OPENPROJECT_URL?: string;\n readonly OPENPROJECT_TOKEN?: string;\n readonly OPENPROJECT_AUTH_MODE?: string;\n readonly OPENPROJECT_ALLOW_WRITE?: string;\n readonly OPENPROJECT_DEFAULT_PROJECT?: string;\n}\n\nexport interface ConfigResolutionOptions {\n readonly cwd?: string;\n readonly envFile?: string;\n readonly autoEnv?: boolean;\n readonly profile?: string;\n}\n\nexport function loadConfig(env: EnvReader = process.env): OpctlConfig {\n return loadConfigFromEnv(env);\n}\n\nexport function resolveConfigEnv(processEnv: NodeJS.ProcessEnv, options: ConfigResolutionOptions = {}): EnvReader {\n const cwd = options.cwd ?? process.cwd();\n const autoEnvPath = join(cwd, \".env\");\n const autoEnv = options.autoEnv === false || !existsSync(autoEnvPath) ? {} : loadEnvFile(autoEnvPath);\n const activeProfile = options.profile ? {} : getProfileEnv(undefined, processEnv);\n const selectedProfile = options.profile ? getProfileEnv(options.profile, processEnv) : {};\n const explicitEnv = options.envFile ? loadEnvFile(resolve(cwd, options.envFile)) : {};\n return mergeEnv(autoEnv, activeProfile, selectedProfile, explicitEnv, processEnv);\n}\n\nexport function loadResolvedConfig(processEnv: NodeJS.ProcessEnv, options: ConfigResolutionOptions = {}): OpctlConfig {\n return loadConfigFromEnv(resolveConfigEnv(processEnv, options));\n}\n\nexport function mergeEnv(...layers: readonly EnvReader[]): EnvReader {\n const merged: Record<string, string> = {};\n for (const layer of layers) {\n for (const key of [\"OPENPROJECT_URL\", \"OPENPROJECT_TOKEN\", \"OPENPROJECT_AUTH_MODE\", \"OPENPROJECT_ALLOW_WRITE\", \"OPENPROJECT_DEFAULT_PROJECT\"] as const) {\n const value = layer[key];\n if (value !== undefined) merged[key] = value;\n }\n }\n return merged;\n}\n\nfunction loadConfigFromEnv(env: EnvReader): OpctlConfig {\n const rawUrl = env.OPENPROJECT_URL;\n const rawToken = env.OPENPROJECT_TOKEN;\n if (!rawUrl || rawUrl.trim() === \"\") throw new ConfigurationError(\"OPENPROJECT_URL is required\");\n if (!rawToken || rawToken.trim() === \"\") throw new ConfigurationError(\"OPENPROJECT_TOKEN is required\");\n\n const authMode = parseAuthMode(env.OPENPROJECT_AUTH_MODE);\n const defaultProject = cleanOptional(env.OPENPROJECT_DEFAULT_PROJECT);\n return {\n baseUrl: normalizeBaseUrl(rawUrl),\n token: rawToken,\n authMode,\n allowWrite: env.OPENPROJECT_ALLOW_WRITE === \"1\",\n ...(defaultProject ? { defaultProject } : {}),\n };\n}\n\nexport function normalizeBaseUrl(rawUrl: string): string {\n let parsed: URL;\n try {\n parsed = new URL(rawUrl.trim());\n } catch {\n throw new ConfigurationError(\"OPENPROJECT_URL must be an absolute URL\");\n }\n if (parsed.protocol !== \"https:\" && parsed.protocol !== \"http:\") {\n throw new ConfigurationError(\"OPENPROJECT_URL must use http or https\");\n }\n parsed.hash = \"\";\n parsed.search = \"\";\n const withoutTrailing = parsed.toString().replace(/\\/+$/, \"\");\n return withoutTrailing;\n}\n\nfunction parseAuthMode(raw: string | undefined): AuthMode {\n if (!raw || raw.trim() === \"\") return \"bearer\";\n const normalized = raw.trim().toLowerCase();\n if (normalized === \"bearer\" || normalized === \"basic\") return normalized;\n throw new ConfigurationError(\"OPENPROJECT_AUTH_MODE must be bearer or basic\");\n}\n\nfunction cleanOptional(raw: string | undefined): string | undefined {\n if (!raw) return undefined;\n const value = raw.trim();\n return value === \"\" ? undefined : value;\n}\n","import type { Command } from \"commander\";\nimport { OpenProjectClient } from \"../client/openProjectClient.js\";\nimport { loadResolvedConfig, resolveConfigEnv, type ConfigResolutionOptions } from \"../config.js\";\nimport { stableJson } from \"../output/json.js\";\n\nexport interface CommandContext {\n readonly stdout: Pick<NodeJS.WriteStream, \"write\">;\n readonly stderr: Pick<NodeJS.WriteStream, \"write\">;\n readonly env: NodeJS.ProcessEnv;\n readonly fetchImpl?: typeof fetch;\n readonly cwd?: string;\n}\n\nexport function globalConfigOptions(command: Command): ConfigResolutionOptions {\n const opts = rootCommand(command).opts<Record<string, unknown>>();\n return {\n ...(typeof opts.env === \"string\" ? { envFile: opts.env } : {}),\n ...(opts.env === false ? { autoEnv: false } : {}),\n ...(typeof opts.profile === \"string\" ? { profile: opts.profile } : {}),\n };\n}\n\nexport function resolvedEnv(context: CommandContext, command: Command): NodeJS.ProcessEnv {\n return resolveConfigEnv(context.env, configOptions(context, command)) as NodeJS.ProcessEnv;\n}\n\nexport function createClient(context: CommandContext, command?: Command): OpenProjectClient {\n const config = loadResolvedConfig(context.env, command ? configOptions(context, command) : configOptions(context));\n return new OpenProjectClient({\n config,\n ...(context.fetchImpl ? { fetchImpl: context.fetchImpl } : {}),\n });\n}\n\nexport function writeOutput(context: CommandContext, value: unknown, json: boolean, renderText: () => string, token?: string): void {\n context.stdout.write(json ? stableJson(value, token ?? context.env.OPENPROJECT_TOKEN) : renderText());\n}\n\nfunction configOptions(context: CommandContext, command?: Command): ConfigResolutionOptions {\n return {\n ...(context.cwd ? { cwd: context.cwd } : {}),\n ...(command ? globalConfigOptions(command) : {}),\n };\n}\n\nfunction rootCommand(command: Command): Command {\n let current = command;\n while (current.parent) current = current.parent;\n return current;\n}\n\nexport function booleanOption(command: Command, name: string): boolean {\n return Boolean(command.opts<Record<string, unknown>>()[name]);\n}\n","import type { Command } from \"commander\";\nimport { asObject } from \"../client/hal.js\";\nimport { stableJson } from \"../output/json.js\";\nimport { renderKeyValue } from \"../output/text.js\";\nimport { createClient, resolvedEnv, type CommandContext } from \"./context.js\";\n\nexport function registerApiRoot(program: Command, context: CommandContext): void {\n program\n .command(\"api-root\")\n .description(\"Show compact OpenProject API root links\")\n .option(\"--json\", \"emit JSON\")\n .action(async (options: { json?: boolean }, command: Command) => {\n const root = await createClient(context, command).getApiRoot();\n const links = compactLinks(root);\n context.stdout.write(options.json ? stableJson(links, resolvedEnv(context, command).OPENPROJECT_TOKEN) : renderKeyValue(links));\n });\n}\n\nexport function compactLinks(root: unknown): Record<string, string> {\n const links = asObject(asObject(root)?._links) ?? {};\n const output: Record<string, string> = {};\n for (const [name, raw] of Object.entries(links)) {\n const link = Array.isArray(raw) ? asObject(raw[0]) : asObject(raw);\n if (typeof link?.href === \"string\") output[name] = link.href;\n }\n return output;\n}\n","import type { Command } from \"commander\";\nimport { createClient, type CommandContext, writeOutput } from \"./context.js\";\nimport { renderKeyValue } from \"../output/text.js\";\n\nexport function registerMe(program: Command, context: CommandContext): void {\n program\n .command(\"me\")\n .description(\"Show the authenticated OpenProject user\")\n .option(\"--json\", \"emit JSON\")\n .action(async (options: { json?: boolean }, command: Command) => {\n const me = await createClient(context, command).getMe();\n writeOutput(context, me, Boolean(options.json), () => renderKeyValue(me as unknown as Record<string, unknown>));\n });\n}\n","export function renderTable(rows: readonly object[], columns: readonly string[]): string {\n const widths = columns.map((column) => Math.max(column.length, ...rows.map((row) => cell((row as Record<string, unknown>)[column]).length)));\n const header = columns.map((column, index) => column.padEnd(widths[index] ?? column.length)).join(\" \");\n const divider = widths.map((width) => \"-\".repeat(width)).join(\" \");\n const body = rows.map((row) => columns.map((column, index) => cell((row as Record<string, unknown>)[column]).padEnd(widths[index] ?? 0)).join(\" \"));\n return `${[header, divider, ...body].join(\"\\n\")}\\n`;\n}\n\nfunction cell(value: unknown): string {\n if (value === undefined || value === null) return \"\";\n return String(value);\n}\n","import type { Command } from \"commander\";\nimport { renderTable } from \"../output/table.js\";\nimport { createClient, type CommandContext, writeOutput } from \"./context.js\";\n\nexport function registerProjects(program: Command, context: CommandContext): void {\n program\n .command(\"projects\")\n .description(\"List visible OpenProject projects\")\n .option(\"--json\", \"emit JSON\")\n .option(\"--page-size <n>\", \"page size\", Number)\n .action(async (options: { json?: boolean; pageSize?: number }, command: Command) => {\n const projects = await createClient(context, command).listProjects(options.pageSize === undefined ? {} : { pageSize: options.pageSize });\n writeOutput(context, projects, Boolean(options.json), () => renderTable(projects.elements, [\"id\", \"identifier\", \"name\", \"href\"]));\n });\n}\n","import { mkdir, writeFile } from \"node:fs/promises\";\nimport { dirname } from \"node:path\";\nimport { createAuthorizationHeader, redactSecrets } from \"../src/client/auth.js\";\nimport { OpenApiGenerationError } from \"../src/client/errors.js\";\nimport { normalizeBaseUrl, type AuthMode } from \"../src/config.js\";\n\nexport interface PullSpecOptions {\n readonly env?: NodeJS.ProcessEnv;\n readonly fetchImpl?: typeof fetch;\n readonly outputPath?: string;\n readonly timeoutMs?: number;\n readonly stdout?: Pick<typeof process.stdout, \"write\">;\n}\n\nexport interface PullSpecResult {\n readonly sourceHost: string;\n readonly outputPath: string;\n readonly title: string;\n readonly version: string;\n}\n\nexport async function pullOpenApiSpec(options: PullSpecOptions = {}): Promise<PullSpecResult> {\n const env = options.env ?? process.env;\n const outputPath = options.outputPath ?? \"openapi/openproject.json\";\n const fetchImpl = options.fetchImpl ?? fetch;\n const rawUrl = env.OPENPROJECT_URL;\n if (!rawUrl || rawUrl.trim() === \"\") throw new OpenApiGenerationError(\"OPENPROJECT_URL is required to pull the OpenProject spec\");\n const baseUrl = normalizeBaseUrl(rawUrl);\n const authMode = parseAuthMode(env.OPENPROJECT_AUTH_MODE);\n const headers: Record<string, string> = { Accept: \"application/json\" };\n if (env.OPENPROJECT_TOKEN && env.OPENPROJECT_TOKEN.trim() !== \"\") {\n headers.Authorization = createAuthorizationHeader(authMode, env.OPENPROJECT_TOKEN);\n }\n\n const specUrl = `${baseUrl}/api/v3/spec.json`;\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), options.timeoutMs ?? 15_000);\n let response: Response;\n try {\n response = await fetchImpl(specUrl, { headers, signal: controller.signal });\n } catch (error) {\n throw new OpenApiGenerationError(`failed to download OpenProject spec from ${new URL(baseUrl).host}: ${error instanceof Error ? redactSecrets(error.message, env.OPENPROJECT_TOKEN) : \"network error\"}`);\n } finally {\n clearTimeout(timeout);\n }\n\n if (!response.ok) {\n throw new OpenApiGenerationError(`failed to download OpenProject spec from ${new URL(baseUrl).host}: HTTP ${response.status}`);\n }\n\n const text = await response.text();\n let spec: unknown;\n try {\n spec = JSON.parse(text);\n } catch {\n throw new OpenApiGenerationError(\"OpenProject spec response was not valid JSON; spec.yml is not supported by this downloader\");\n }\n if (!spec || typeof spec !== \"object\") throw new OpenApiGenerationError(\"OpenProject spec response was not an object\");\n const info = \"info\" in spec && typeof spec.info === \"object\" && spec.info !== null ? spec.info : undefined;\n const title = info && \"title\" in info && typeof info.title === \"string\" ? info.title : \"OpenProject API\";\n const version = info && \"version\" in info && typeof info.version === \"string\" ? info.version : \"unknown\";\n\n await mkdir(dirname(outputPath), { recursive: true });\n await writeFile(outputPath, `${JSON.stringify(spec, null, 2)}\\n`, \"utf8\");\n const result = { sourceHost: new URL(baseUrl).host, outputPath, title, version };\n options.stdout?.write(`Downloaded OpenProject spec from ${result.sourceHost} to ${result.outputPath} (${result.title} ${result.version})\\n`);\n return result;\n}\n\nfunction parseAuthMode(raw: string | undefined): AuthMode {\n const normalized = raw?.trim().toLowerCase() ?? \"\";\n if (normalized === \"\" || normalized === \"bearer\") return \"bearer\";\n if (normalized === \"basic\") return \"basic\";\n throw new OpenApiGenerationError(\"OPENPROJECT_AUTH_MODE must be bearer or basic\");\n}\n\nif (import.meta.url === `file://${process.argv[1]}`) {\n pullOpenApiSpec({ stdout: process.stdout }).catch((error: unknown) => {\n const message = error instanceof Error ? error.message : \"failed to pull OpenProject spec\";\n process.stderr.write(`${redactSecrets(message, process.env.OPENPROJECT_TOKEN)}\\n`);\n process.exitCode = 8;\n });\n}\n","import type { Command } from \"commander\";\nimport { pullOpenApiSpec } from \"../../scripts/pull-openapi-spec.js\";\nimport { resolvedEnv, type CommandContext } from \"./context.js\";\n\nexport function registerSpec(program: Command, context: CommandContext): void {\n const spec = program.command(\"spec\").description(\"OpenAPI spec utilities\");\n spec.command(\"pull\")\n .description(\"Download OpenProject /api/v3/spec.json safely\")\n .action(async (_options: unknown, command: Command) => {\n await pullOpenApiSpec({ env: resolvedEnv(context, command), stdout: context.stdout, ...(context.fetchImpl ? { fetchImpl: context.fetchImpl } : {}) });\n });\n}\n","import { OpctlError, EXIT_CODES } from \"../client/errors.js\";\n\nexport type WorkPackageField = \"id\" | \"subject\" | \"status\" | \"type\" | \"assignee\" | \"project\" | \"href\" | \"updatedAt\" | \"description\" | \"shortDescription\" | \"attachmentsCount\" | \"lockVersion\";\nexport type OutputMode = \"text\" | \"table\" | \"compact\" | \"json\" | \"jsonl\" | \"rawJson\";\n\nexport const DEFAULT_COMPACT_FIELDS: readonly WorkPackageField[] = [\"id\", \"subject\", \"status\", \"assignee\", \"updatedAt\"];\nexport const DEFAULT_TABLE_FIELDS: readonly WorkPackageField[] = [\"id\", \"subject\", \"status\", \"assignee\", \"project\", \"updatedAt\"];\nexport const DEFAULT_CHECK_FIELDS: readonly WorkPackageField[] = [\"id\", \"subject\", \"status\", \"assignee\", \"shortDescription\", \"attachmentsCount\"];\nexport const DETAIL_FIELDS: readonly WorkPackageField[] = [\"id\", \"subject\", \"status\", \"type\", \"assignee\", \"project\", \"href\", \"updatedAt\", \"description\", \"shortDescription\", \"attachmentsCount\", \"lockVersion\"];\n\nconst SUPPORTED_FIELDS: Record<WorkPackageField, true> = {\n assignee: true,\n attachmentsCount: true,\n description: true,\n href: true,\n id: true,\n lockVersion: true,\n project: true,\n shortDescription: true,\n status: true,\n subject: true,\n type: true,\n updatedAt: true,\n};\n\nconst FIELD_ALIASES: Record<string, WorkPackageField> = {\n title: \"subject\",\n url: \"href\",\n};\n\nexport interface OutputFlagOptions {\n readonly json?: boolean;\n readonly jsonl?: boolean;\n readonly table?: boolean;\n readonly compact?: boolean;\n readonly rawJson?: boolean;\n}\n\nexport function parseOutputMode(options: OutputFlagOptions): OutputMode {\n const enabled = [options.json, options.jsonl, options.table, options.compact, options.rawJson].filter(Boolean).length;\n if (enabled > 1) throw new OpctlError(\"choose only one output mode flag\", EXIT_CODES.validation);\n if (options.rawJson) return \"rawJson\";\n if (options.json) return \"json\";\n if (options.jsonl) return \"jsonl\";\n if (options.compact) return \"compact\";\n if (options.table) return \"table\";\n return \"text\";\n}\n\nexport function parseFields(raw: string | undefined, defaults: readonly WorkPackageField[]): readonly WorkPackageField[] {\n if (!raw || raw.trim() === \"\") return defaults;\n const fields: WorkPackageField[] = [];\n for (const part of raw.split(\",\")) {\n const trimmed = part.trim();\n if (trimmed === \"\") continue;\n const field = FIELD_ALIASES[trimmed] ?? trimmed;\n if (!isSupportedField(field)) throw new OpctlError(`unknown work package field '${trimmed}'. Supported fields: ${Object.keys(SUPPORTED_FIELDS).join(\",\")}`, EXIT_CODES.validation);\n fields.push(field);\n }\n if (fields.length === 0) throw new OpctlError(\"--fields must include at least one field\", EXIT_CODES.validation);\n return fields;\n}\n\nexport function projectFields<T extends Record<string, unknown>>(value: T, fields: readonly WorkPackageField[]): Record<string, unknown> {\n const projected: Record<string, unknown> = {};\n for (const field of fields) projected[field] = value[field];\n return projected;\n}\n\nexport function projectRows<T extends Record<string, unknown>>(rows: readonly T[], fields: readonly WorkPackageField[]): Record<string, unknown>[] {\n return rows.map((row) => projectFields(row, fields));\n}\n\nfunction isSupportedField(field: string): field is WorkPackageField {\n return SUPPORTED_FIELDS[field as WorkPackageField] === true;\n}\n","import type { Command } from \"commander\";\nimport { OpctlError, EXIT_CODES } from \"../client/errors.js\";\nimport { stableJson } from \"../output/json.js\";\nimport { renderKeyValue } from \"../output/text.js\";\nimport { renderTable } from \"../output/table.js\";\nimport { DEFAULT_CHECK_FIELDS, DEFAULT_COMPACT_FIELDS, DEFAULT_TABLE_FIELDS, DETAIL_FIELDS, parseFields, parseOutputMode, projectFields, projectRows, type OutputFlagOptions, type WorkPackageField } from \"../output/fields.js\";\nimport type { NormalizedCollection } from \"../client/pagination.js\";\nimport type { WorkPackageDetail, WorkPackageSummary } from \"../types/domain.js\";\nimport { createClient, resolvedEnv, type CommandContext, writeOutput } from \"./context.js\";\n\ninterface SearchOptions extends OutputFlagOptions {\n readonly fields?: string;\n readonly project?: string;\n readonly subject?: string;\n readonly assigneeMe?: boolean;\n readonly status?: string;\n readonly open?: boolean;\n readonly pageSize?: number;\n}\n\ninterface GetOptions extends OutputFlagOptions {\n readonly ids?: string;\n readonly fields?: string;\n}\n\nexport function registerWorkPackages(program: Command, context: CommandContext): void {\n const wp = program.command(\"wp\").description(\"Work package commands\");\n\n wp.command(\"get\")\n .description(\"Get one or more work packages\")\n .argument(\"[ids...]\", \"work package ids\")\n .option(\"--ids <csv>\", \"comma-separated work package ids\")\n .option(\"--fields <csv>\", \"comma-separated output fields\")\n .option(\"--table\", \"emit table output\")\n .option(\"--compact\", \"emit compact triage table output\")\n .option(\"--json\", \"emit normalized JSON\")\n .option(\"--jsonl\", \"emit one JSON object per line\")\n .option(\"--raw-json\", \"emit raw OpenProject JSON; single work package only\")\n .action(async (ids: string[], options: GetOptions, command: Command) => {\n const numericIds = parseIds(ids, options.ids);\n if (numericIds.length === 0) throw new OpctlError(\"at least one work package id is required\", EXIT_CODES.validation);\n const mode = parseOutputMode(options);\n if (mode === \"rawJson\" && numericIds.length !== 1) throw new OpctlError(\"--raw-json supports exactly one work package id\", EXIT_CODES.validation);\n if (mode !== \"rawJson\") parseFields(options.fields, mode === \"compact\" ? DEFAULT_COMPACT_FIELDS : DETAIL_FIELDS);\n const client = createClient(context, command);\n const token = resolvedEnv(context, command).OPENPROJECT_TOKEN;\n if (mode === \"rawJson\") {\n context.stdout.write(stableJson(await client.getWorkPackageRaw(numericIds[0] as number), token));\n return;\n }\n const workPackages: WorkPackageDetail[] = [];\n for (const id of numericIds) workPackages.push(await client.getWorkPackage(id));\n writeWorkPackageOutput(context, workPackages, options, mode, numericIds.length === 1 && !options.ids, DETAIL_FIELDS, token);\n });\n\n wp.command(\"check\")\n .description(\"Read a triage-oriented issue list for one or more work packages\")\n .argument(\"[ids...]\", \"work package ids\")\n .option(\"--ids <csv>\", \"comma-separated work package ids\")\n .option(\"--fields <csv>\", \"comma-separated output fields\")\n .option(\"--table\", \"emit table output\")\n .option(\"--compact\", \"emit compact table output\")\n .option(\"--json\", \"emit JSON\")\n .option(\"--jsonl\", \"emit one JSON object per line\")\n .action(async (ids: string[], options: Omit<GetOptions, \"rawJson\">, command: Command) => {\n const numericIds = parseIds(ids, options.ids);\n if (numericIds.length === 0) throw new OpctlError(\"at least one work package id is required\", EXIT_CODES.validation);\n const mode = parseOutputMode(options);\n parseFields(options.fields, mode === \"compact\" ? DEFAULT_COMPACT_FIELDS : DEFAULT_CHECK_FIELDS);\n const client = createClient(context, command);\n const workPackages: WorkPackageDetail[] = [];\n for (const id of numericIds) workPackages.push(await client.getWorkPackage(id));\n writeWorkPackageOutput(context, workPackages, options, mode, false, DEFAULT_CHECK_FIELDS, resolvedEnv(context, command).OPENPROJECT_TOKEN);\n });\n\n wp.command(\"search\")\n .description(\"Search work packages\")\n .option(\"--json\", \"emit JSON\")\n .option(\"--jsonl\", \"emit one JSON object per line\")\n .option(\"--table\", \"emit table output\")\n .option(\"--compact\", \"emit compact table output\")\n .option(\"--fields <csv>\", \"comma-separated output fields\")\n .option(\"--project <identifier-or-id>\", \"project identifier or id\")\n .option(\"--subject <text>\", \"subject contains text\")\n .option(\"--assignee-me\", \"filter to current user\")\n .option(\"--status <id-or-open>\", \"status id, or open\")\n .option(\"--open\", \"filter to open work packages\")\n .option(\"--page-size <n>\", \"page size\", Number)\n .action(async (options: SearchOptions, command: Command) => {\n const result = await createClient(context, command).searchWorkPackages(options);\n writeCollectionOutput(context, result, options, resolvedEnv(context, command).OPENPROJECT_TOKEN);\n });\n\n wp.command(\"mine\")\n .description(\"List open work packages assigned to the authenticated user\")\n .option(\"--json\", \"emit JSON\")\n .option(\"--jsonl\", \"emit one JSON object per line\")\n .option(\"--table\", \"emit table output\")\n .option(\"--compact\", \"emit compact table output\")\n .option(\"--fields <csv>\", \"comma-separated output fields\")\n .option(\"--project <identifier-or-id>\", \"project identifier or id\")\n .option(\"--open\", \"filter to open work packages\")\n .option(\"--page-size <n>\", \"page size\", Number)\n .action(async (options: Pick<SearchOptions, \"json\" | \"jsonl\" | \"table\" | \"compact\" | \"fields\" | \"project\" | \"open\" | \"pageSize\">, command: Command) => {\n const result = await createClient(context, command).mine(options);\n writeCollectionOutput(context, result, options, resolvedEnv(context, command).OPENPROJECT_TOKEN);\n });\n\n wp.command(\"comment\")\n .description(\"Add a comment to a work package; requires OPENPROJECT_ALLOW_WRITE=1\")\n .argument(\"<id>\", \"work package id\")\n .argument(\"[message...]\", \"comment message\")\n .option(\"--dry-run\", \"print intended mutation without posting\")\n .option(\"--json\", \"emit JSON\")\n .action(async (id: string, messageParts: string[], options: { dryRun?: boolean; json?: boolean }, command: Command) => {\n const result = await createClient(context, command).commentWorkPackage(parseId(id), messageParts.join(\" \"), Boolean(options.dryRun));\n writeOutput(context, result, Boolean(options.json), () => renderKeyValue(result as unknown as Record<string, unknown>), resolvedEnv(context, command).OPENPROJECT_TOKEN);\n });\n}\n\nfunction writeCollectionOutput(context: CommandContext, result: NormalizedCollection<WorkPackageSummary>, options: SearchOptions | Pick<SearchOptions, \"json\" | \"jsonl\" | \"table\" | \"compact\" | \"fields\" | \"project\" | \"open\" | \"pageSize\">, token: string | undefined): void {\n const mode = parseOutputMode(options);\n const defaults = mode === \"compact\" ? DEFAULT_COMPACT_FIELDS : DEFAULT_TABLE_FIELDS;\n const fields = parseFields(options.fields, defaults);\n const elements = projectRows(result.elements as unknown as Record<string, unknown>[], fields);\n if (mode === \"jsonl\") {\n context.stdout.write(elements.map((element) => JSON.stringify(element)).join(\"\\n\") + (elements.length > 0 ? \"\\n\" : \"\"));\n return;\n }\n if (mode === \"json\") {\n const output = options.fields ? { ...result, elements } : result;\n context.stdout.write(stableJson(output, token));\n return;\n }\n context.stdout.write(renderTable(elements, fields));\n}\n\nfunction writeWorkPackageOutput(context: CommandContext, workPackages: readonly WorkPackageDetail[], options: GetOptions | Omit<GetOptions, \"rawJson\">, mode: string, singleObject: boolean, defaultFields: readonly WorkPackageField[], token: string | undefined): void {\n const fields = parseFields(options.fields, mode === \"compact\" ? DEFAULT_COMPACT_FIELDS : defaultFields);\n const projected = options.fields || mode === \"table\" || mode === \"compact\" || mode === \"jsonl\" ? projectRows(workPackages as unknown as Record<string, unknown>[], fields) : workPackages;\n if (mode === \"jsonl\") {\n const rows = projectRows(workPackages as unknown as Record<string, unknown>[], fields);\n context.stdout.write(rows.map((row) => JSON.stringify(row)).join(\"\\n\") + (rows.length > 0 ? \"\\n\" : \"\"));\n return;\n }\n if (mode === \"json\") {\n context.stdout.write(stableJson(singleObject ? projected[0] : projected, token));\n return;\n }\n if (singleObject && mode === \"text\" && !options.fields) {\n context.stdout.write(renderKeyValue(workPackages[0] as unknown as Record<string, unknown>));\n return;\n }\n context.stdout.write(renderTable(projectRows(workPackages as unknown as Record<string, unknown>[], fields), fields));\n}\n\nfunction parseIds(positionals: readonly string[], csv: string | undefined): number[] {\n const seen = new Set<number>();\n const ids: number[] = [];\n for (const raw of [...positionals, ...splitCsv(csv)]) {\n const id = parseId(raw);\n if (seen.has(id)) continue;\n seen.add(id);\n ids.push(id);\n }\n return ids;\n}\n\nfunction splitCsv(csv: string | undefined): string[] {\n if (!csv) return [];\n return csv.split(\",\").map((part) => part.trim()).filter((part) => part !== \"\");\n}\n\nfunction parseId(id: string): number {\n const parsed = Number(id);\n if (!Number.isInteger(parsed) || parsed < 1) throw new OpctlError(\"work package id must be a positive integer\", EXIT_CODES.validation);\n return parsed;\n}\n","import type { Command } from \"commander\";\nimport { stableJson } from \"../output/json.js\";\nimport { renderKeyValue } from \"../output/text.js\";\nimport { renderTable } from \"../output/table.js\";\nimport { listProfiles, redactProfile, setProfile, showProfile, unsetProfile, useProfile, type Profile } from \"../config/profiles.js\";\nimport type { CommandContext } from \"./context.js\";\n\ninterface SetProfileOptions {\n readonly url?: string;\n readonly authMode?: string;\n readonly defaultProject?: string;\n readonly token?: string;\n readonly json?: boolean;\n}\n\nexport function registerProfile(program: Command, context: CommandContext): void {\n const profile = program.command(\"profile\").description(\"Saved non-write OpenProject connection profiles\");\n\n profile.command(\"list\")\n .description(\"List saved profiles\")\n .option(\"--json\", \"emit JSON\")\n .action((options: { readonly json?: boolean }) => {\n const rows = listProfiles(context.env).map((entry) => ({ name: entry.name, active: entry.active ? \"yes\" : \"\", ...redactProfile(entry.profile) }));\n context.stdout.write(options.json ? stableJson(rows) : renderTable(rows, [\"name\", \"active\", \"url\", \"authMode\", \"defaultProject\", \"token\"]));\n });\n\n profile.command(\"show\")\n .description(\"Show a saved profile with secrets redacted\")\n .argument(\"[name]\", \"profile name; defaults to active profile\")\n .option(\"--json\", \"emit JSON\")\n .action((name: string | undefined, options: { readonly json?: boolean }) => {\n const result = showProfile(name, context.env);\n const output = result.profile ? { name: result.name, ...redactProfile(result.profile) } : { name: undefined };\n context.stdout.write(options.json ? stableJson(output) : renderKeyValue(output));\n });\n\n profile.command(\"set\")\n .description(\"Create or replace a saved profile\")\n .argument(\"<name>\", \"profile name\")\n .option(\"--url <url>\", \"OpenProject base URL\")\n .option(\"--auth-mode <mode>\", \"auth mode: bearer or basic\")\n .option(\"--default-project <id>\", \"default project identifier or id\")\n .option(\"--token <token>\", \"OpenProject API token; stored in a 0600 profile file\")\n .option(\"--json\", \"emit JSON\")\n .action((name: string, options: SetProfileOptions) => {\n const profileValue: Profile = {\n ...(options.url ? { url: options.url } : {}),\n ...(options.authMode ? { authMode: options.authMode } : {}),\n ...(options.defaultProject ? { defaultProject: options.defaultProject } : {}),\n ...(options.token ? { token: options.token } : {}),\n };\n setProfile(name, profileValue, context.env);\n const output = { name, ...redactProfile(profileValue) };\n context.stdout.write(options.json ? stableJson(output) : `${name}\\n`);\n });\n\n profile.command(\"use\")\n .description(\"Set the active profile\")\n .argument(\"<name>\", \"profile name\")\n .option(\"--json\", \"emit JSON\")\n .action((name: string, options: { readonly json?: boolean }) => {\n useProfile(name, context.env);\n const output = { activeProfile: name };\n context.stdout.write(options.json ? stableJson(output) : `active profile: ${name}\\n`);\n });\n\n profile.command(\"unset\")\n .description(\"Delete a saved profile\")\n .argument(\"<name>\", \"profile name\")\n .option(\"--json\", \"emit JSON\")\n .action((name: string, options: { readonly json?: boolean }) => {\n unsetProfile(name, context.env);\n const output = { unset: name };\n context.stdout.write(options.json ? stableJson(output) : `unset profile: ${name}\\n`);\n });\n}\n","{\n \"name\": \"opctl\",\n \"version\": \"0.1.4\",\n \"description\": \"Conservative local CLI bridge for OpenProject API v3\",\n \"type\": \"module\",\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://github.com/hewel/op-cli.git\"\n },\n \"bin\": {\n \"opctl\": \"dist/cli.js\"\n },\n \"files\": [\n \"dist\"\n ],\n \"scripts\": {\n \"dev\": \"tsx src/cli.ts\",\n \"build\": \"npm run typecheck && vite build\",\n \"typecheck\": \"tsc --noEmit\",\n \"test\": \"vitest run\",\n \"openapi:pull\": \"tsx scripts/pull-openapi-spec.ts\",\n \"openapi:generate\": \"tsx scripts/generate-openapi-types.ts\",\n \"openapi:update\": \"npm run openapi:pull && npm run openapi:generate\"\n },\n \"keywords\": [\n \"openproject\",\n \"cli\"\n ],\n \"author\": \"\",\n \"license\": \"ISC\",\n \"dependencies\": {\n \"commander\": \"latest\",\n \"openapi-fetch\": \"latest\"\n },\n \"devDependencies\": {\n \"@types/node\": \"latest\",\n \"openapi-typescript\": \"latest\",\n \"tsx\": \"latest\",\n \"typescript\": \"latest\",\n \"vite\": \"latest\",\n \"vitest\": \"latest\",\n \"yaml\": \"^2.9.0\"\n },\n \"packageManager\": \"pnpm@11.1.3\"\n}\n","#!/usr/bin/env node\nimport { realpathSync } from \"node:fs\";\nimport { fileURLToPath } from \"node:url\";\nimport { Command } from \"commander\";\nimport { redactSecrets } from \"./client/auth.js\";\nimport { EXIT_CODES, toOpctlError } from \"./client/errors.js\";\nimport { stableJson } from \"./output/json.js\";\nimport { registerApiRoot } from \"./commands/apiRoot.js\";\nimport { registerMe } from \"./commands/me.js\";\nimport { registerProjects } from \"./commands/projects.js\";\nimport { registerSpec } from \"./commands/spec.js\";\nimport { registerWorkPackages } from \"./commands/workPackages.js\";\nimport { registerProfile } from \"./commands/profile.js\";\nimport type { CommandContext } from \"./commands/context.js\";\nimport pkg from \"../package.json\" with { type: \"json\" };\n\nexport function buildProgram(context: CommandContext): Command {\n const program = new Command();\n program\n .name(\"opctl\")\n .description(\"Conservative local CLI bridge for OpenProject API v3\")\n .version(pkg.version)\n .option(\"--env <path>\", \"load OpenProject configuration from dotenv-style file\")\n .option(\"--no-env\", \"disable automatic .env loading from the current working directory\")\n .option(\"--profile <name>\", \"use a saved profile for this invocation\")\n .showHelpAfterError()\n .configureOutput({\n writeOut: (text) => context.stdout.write(text),\n writeErr: (text) => context.stderr.write(text),\n });\n registerMe(program, context);\n registerApiRoot(program, context);\n registerProjects(program, context);\n registerWorkPackages(program, context);\n registerProfile(program, context);\n registerSpec(program, context);\n return program;\n}\n\nexport async function run(argv: readonly string[], context: CommandContext): Promise<number> {\n try {\n await buildProgram(context).parseAsync(argv, { from: \"node\" });\n return EXIT_CODES.success;\n } catch (error) {\n const opctlError = toOpctlError(error);\n const wantsJson = argv.includes(\"--json\");\n if (wantsJson) context.stderr.write(stableJson({ error: opctlError.message, exitCode: opctlError.exitCode }, context.env.OPENPROJECT_TOKEN));\n else context.stderr.write(`${redactSecrets(opctlError.message, context.env.OPENPROJECT_TOKEN)}\\n`);\n return opctlError.exitCode;\n }\n}\n\nexport function isCliEntrypoint(metaUrl: string, argvPath: string | undefined = process.argv[1]): boolean {\n if (!argvPath) return false;\n try {\n return realpathSync(fileURLToPath(metaUrl)) === realpathSync(argvPath);\n } catch {\n return false;\n }\n}\n\nif (isCliEntrypoint(import.meta.url)) {\n const exitCode = await run(process.argv, { stdout: process.stdout, stderr: process.stderr, env: process.env });\n process.exitCode = exitCode;\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opctl",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "Conservative local CLI bridge for OpenProject API v3",
|
|
5
5
|
"type": "module",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/hewel/op-cli.git"
|
|
9
|
+
},
|
|
6
10
|
"bin": {
|
|
7
|
-
"opctl": "
|
|
11
|
+
"opctl": "dist/cli.js"
|
|
8
12
|
},
|
|
9
13
|
"files": [
|
|
10
14
|
"dist"
|