@floh-solutions/pharos-cli 0.4.0 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +381 -7
- package/dist/cli.js.map +1 -1
- package/dist/commands/attach.d.ts +30 -0
- package/dist/commands/attach.d.ts.map +1 -0
- package/dist/commands/attach.js +55 -0
- package/dist/commands/attach.js.map +1 -0
- package/dist/commands/create.d.ts +35 -0
- package/dist/commands/create.d.ts.map +1 -0
- package/dist/commands/create.js +82 -0
- package/dist/commands/create.js.map +1 -0
- package/dist/commands/detach.d.ts +34 -0
- package/dist/commands/detach.d.ts.map +1 -0
- package/dist/commands/detach.js +117 -0
- package/dist/commands/detach.js.map +1 -0
- package/dist/commands/discover.d.ts +66 -0
- package/dist/commands/discover.d.ts.map +1 -0
- package/dist/commands/discover.js +128 -0
- package/dist/commands/discover.js.map +1 -0
- package/dist/commands/hooks.d.ts +24 -0
- package/dist/commands/hooks.d.ts.map +1 -0
- package/dist/commands/hooks.js +184 -0
- package/dist/commands/hooks.js.map +1 -0
- package/dist/commands/image.d.ts +44 -0
- package/dist/commands/image.d.ts.map +1 -0
- package/dist/commands/image.js +114 -0
- package/dist/commands/image.js.map +1 -0
- package/dist/commands/import.d.ts +51 -0
- package/dist/commands/import.d.ts.map +1 -0
- package/dist/commands/import.js +190 -0
- package/dist/commands/import.js.map +1 -0
- package/dist/commands/link.d.ts +40 -0
- package/dist/commands/link.d.ts.map +1 -0
- package/dist/commands/link.js +150 -0
- package/dist/commands/link.js.map +1 -0
- package/dist/commands/people.d.ts +39 -0
- package/dist/commands/people.d.ts.map +1 -0
- package/dist/commands/people.js +64 -0
- package/dist/commands/people.js.map +1 -0
- package/dist/commands/query.d.ts +43 -0
- package/dist/commands/query.d.ts.map +1 -0
- package/dist/commands/query.js +202 -0
- package/dist/commands/query.js.map +1 -0
- package/dist/commands/recycle.d.ts +10 -0
- package/dist/commands/recycle.d.ts.map +1 -0
- package/dist/commands/recycle.js +118 -0
- package/dist/commands/recycle.js.map +1 -0
- package/dist/commands/update.d.ts +52 -0
- package/dist/commands/update.d.ts.map +1 -0
- package/dist/commands/update.js +181 -0
- package/dist/commands/update.js.map +1 -0
- package/dist/commands/wiki.d.ts.map +1 -1
- package/dist/commands/wiki.js +133 -2
- package/dist/commands/wiki.js.map +1 -1
- package/dist/output.d.ts.map +1 -1
- package/dist/output.js +32 -2
- package/dist/output.js.map +1 -1
- package/dist/wiql-build.d.ts +92 -0
- package/dist/wiql-build.d.ts.map +1 -0
- package/dist/wiql-build.js +147 -0
- package/dist/wiql-build.js.map +1 -0
- package/package.json +3 -3
- package/skill/SKILL.md +315 -65
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"create.d.ts","sourceRoot":"","sources":["../../src/commands/create.ts"],"names":[],"mappings":"AAOA,OAAO,EAA8B,KAAK,QAAQ,EAAE,KAAK,EAAE,EAAE,MAAM,cAAc,CAAC;AAClF,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAG7C;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,MAAM,WAAW,oBAAoB;IACnC,KAAK,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,KAAK,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAC;IAC7B,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAC;IAC7B,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;IAC1B,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;IACxB,kEAAkE;IAClE,WAAW,EAAE,MAAM,GAAG,SAAS,CAAC;CACjC;AAED,wBAAsB,SAAS,CAC7B,EAAE,EAAE,EAAE,EACN,OAAO,EAAE,OAAO,EAChB,WAAW,EAAE,SAAS,MAAM,EAAE,EAC9B,OAAO,EAAE,oBAAoB,GAC5B,OAAO,CAAC,QAAQ,CAAC,CA+EnB"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { addParent, setField, setMarkdown, } from "@floh-solutions/ado-core";
|
|
2
|
+
import { emit, emitText, usageError } from "../output.js";
|
|
3
|
+
import { SHORTHAND } from "./update.js";
|
|
4
|
+
export async function runCreate(io, session, positionals, options) {
|
|
5
|
+
const type = positionals[0];
|
|
6
|
+
if (type === undefined) {
|
|
7
|
+
throw usageError('Which type? `pharos create Task --title "…"`. Run `pharos types` for what this project has.');
|
|
8
|
+
}
|
|
9
|
+
if (options.title === undefined || options.title.trim() === "") {
|
|
10
|
+
throw usageError("A work item needs a --title.");
|
|
11
|
+
}
|
|
12
|
+
const ops = [setField("System.Title", options.title)];
|
|
13
|
+
for (const [flag, field] of Object.entries(SHORTHAND)) {
|
|
14
|
+
if (field === "System.Title")
|
|
15
|
+
continue;
|
|
16
|
+
const value = options[flag];
|
|
17
|
+
if (typeof value === "string" && value !== "")
|
|
18
|
+
ops.push(setField(field, value));
|
|
19
|
+
}
|
|
20
|
+
for (const pair of options.fields) {
|
|
21
|
+
const at = pair.indexOf("=");
|
|
22
|
+
if (at <= 0) {
|
|
23
|
+
throw usageError(`--field wants Name=value, not "${pair}".`);
|
|
24
|
+
}
|
|
25
|
+
ops.push(setField(pair.slice(0, at).trim(), pair.slice(at + 1)));
|
|
26
|
+
}
|
|
27
|
+
// Semicolon-joined, which is how Azure DevOps stores them. Passing an array
|
|
28
|
+
// silently stores the string "a,b" as one tag.
|
|
29
|
+
if (options.tags.length > 0)
|
|
30
|
+
ops.push(setField("System.Tags", options.tags.join("; ")));
|
|
31
|
+
if (options.description !== undefined && options.description !== "") {
|
|
32
|
+
ops.push(...setMarkdown("System.Description", options.description));
|
|
33
|
+
}
|
|
34
|
+
// Resolved rather than composed from the id, and it earns the extra GET
|
|
35
|
+
// twice over: `addParent` wants the parent's canonical url, and a parent that
|
|
36
|
+
// does not exist fails HERE with "no work item 9999" instead of arriving as
|
|
37
|
+
// an opaque 400 about a relation.
|
|
38
|
+
let parentTitle;
|
|
39
|
+
if (options.parent !== undefined) {
|
|
40
|
+
const parent = await session.client.workItems.get(options.parent);
|
|
41
|
+
const url = parent.url;
|
|
42
|
+
if (typeof url !== "string" || url === "") {
|
|
43
|
+
throw usageError(`Work item #${options.parent} did not report a url, so it cannot be linked as a parent.`);
|
|
44
|
+
}
|
|
45
|
+
ops.push(addParent(url));
|
|
46
|
+
const title = parent.fields?.["System.Title"];
|
|
47
|
+
if (typeof title === "string")
|
|
48
|
+
parentTitle = title;
|
|
49
|
+
}
|
|
50
|
+
if (session.previewOnly) {
|
|
51
|
+
return report(io, session, {
|
|
52
|
+
applied: false,
|
|
53
|
+
dryRun: true,
|
|
54
|
+
type,
|
|
55
|
+
title: options.title,
|
|
56
|
+
...(options.parent === undefined ? {} : { parent: options.parent }),
|
|
57
|
+
operations: ops,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
const created = await session.client.workItems.create(type, ops, {
|
|
61
|
+
suppressNotifications: true,
|
|
62
|
+
});
|
|
63
|
+
return report(io, session, {
|
|
64
|
+
applied: true,
|
|
65
|
+
id: created.id,
|
|
66
|
+
type,
|
|
67
|
+
title: options.title,
|
|
68
|
+
rev: created.rev,
|
|
69
|
+
url: created.url,
|
|
70
|
+
...(options.parent === undefined
|
|
71
|
+
? {}
|
|
72
|
+
: { parent: options.parent, parentTitle: parentTitle ?? null }),
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
function report(io, session, result) {
|
|
76
|
+
if (!session.pretty)
|
|
77
|
+
return emit(io, result);
|
|
78
|
+
const id = typeof result["id"] === "number" ? `#${result["id"]}` : "(not created)";
|
|
79
|
+
const parent = result["parent"] === undefined ? "" : ` under #${String(result["parent"])}`;
|
|
80
|
+
return emitText(io, `${id} · ${result.type}${parent} — ${result.title ?? ""}`);
|
|
81
|
+
}
|
|
82
|
+
//# sourceMappingURL=create.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"create.js","sourceRoot":"","sources":["../../src/commands/create.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,SAAS,EACT,QAAQ,EACR,WAAW,GAEZ,MAAM,0BAA0B,CAAC;AAElC,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,UAAU,EAA0B,MAAM,cAAc,CAAC;AAElF,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAmCxC,MAAM,CAAC,KAAK,UAAU,SAAS,CAC7B,EAAM,EACN,OAAgB,EAChB,WAA8B,EAC9B,OAA6B;IAE7B,MAAM,IAAI,GAAG,WAAW,CAAC,CAAC,CAAC,CAAC;IAC5B,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;QACvB,MAAM,UAAU,CACd,6FAA6F,CAC9F,CAAC;IACJ,CAAC;IACD,IAAI,OAAO,CAAC,KAAK,KAAK,SAAS,IAAI,OAAO,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;QAC/D,MAAM,UAAU,CAAC,8BAA8B,CAAC,CAAC;IACnD,CAAC;IAED,MAAM,GAAG,GAAyB,CAAC,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC;IAE5E,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE,CAAC;QACtD,IAAI,KAAK,KAAK,cAAc;YAAE,SAAS;QACvC,MAAM,KAAK,GAAG,OAAO,CAAC,IAAkC,CAAC,CAAC;QAC1D,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,EAAE;YAAE,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC;IAClF,CAAC;IAED,KAAK,MAAM,IAAI,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;QAClC,MAAM,EAAE,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QAC7B,IAAI,EAAE,IAAI,CAAC,EAAE,CAAC;YACZ,MAAM,UAAU,CAAC,kCAAkC,IAAI,IAAI,CAAC,CAAC;QAC/D,CAAC;QACD,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,EAAE,IAAI,CAAC,KAAK,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IACnE,CAAC;IAED,4EAA4E;IAC5E,+CAA+C;IAC/C,IAAI,OAAO,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC;QAAE,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAExF,IAAI,OAAO,CAAC,WAAW,KAAK,SAAS,IAAI,OAAO,CAAC,WAAW,KAAK,EAAE,EAAE,CAAC;QACpE,GAAG,CAAC,IAAI,CAAC,GAAG,WAAW,CAAC,oBAAoB,EAAE,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC;IACtE,CAAC;IAED,wEAAwE;IACxE,8EAA8E;IAC9E,4EAA4E;IAC5E,kCAAkC;IAClC,IAAI,WAA+B,CAAC;IACpC,IAAI,OAAO,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;QACjC,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,MAAM,CAAC,SAAS,CAAC,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;QAClE,MAAM,GAAG,GAAG,MAAM,CAAC,GAAG,CAAC;QACvB,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,KAAK,EAAE,EAAE,CAAC;YAC1C,MAAM,UAAU,CACd,cAAc,OAAO,CAAC,MAAM,4DAA4D,CACzF,CAAC;QACJ,CAAC;QACD,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC;QACzB,MAAM,KAAK,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,cAAc,CAAC,CAAC;QAC9C,IAAI,OAAO,KAAK,KAAK,QAAQ;YAAE,WAAW,GAAG,KAAK,CAAC;IACrD,CAAC;IAED,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC;QACxB,OAAO,MAAM,CAAC,EAAE,EAAE,OAAO,EAAE;YACzB,OAAO,EAAE,KAAK;YACd,MAAM,EAAE,IAAI;YACZ,IAAI;YACJ,KAAK,EAAE,OAAO,CAAC,KAAK;YACpB,GAAG,CAAC,OAAO,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC;YACnE,UAAU,EAAE,GAAG;SAChB,CAAC,CAAC;IACL,CAAC;IAED,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,EAAE,GAAG,EAAE;QAC/D,qBAAqB,EAAE,IAAI;KAC5B,CAAC,CAAC;IAEH,OAAO,MAAM,CAAC,EAAE,EAAE,OAAO,EAAE;QACzB,OAAO,EAAE,IAAI;QACb,EAAE,EAAE,OAAO,CAAC,EAAE;QACd,IAAI;QACJ,KAAK,EAAE,OAAO,CAAC,KAAK;QACpB,GAAG,EAAE,OAAO,CAAC,GAAG;QAChB,GAAG,EAAE,OAAO,CAAC,GAAG;QAChB,GAAG,CAAC,OAAO,CAAC,MAAM,KAAK,SAAS;YAC9B,CAAC,CAAC,EAAE;YACJ,CAAC,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,WAAW,EAAE,WAAW,IAAI,IAAI,EAAE,CAAC;KAClE,CAAC,CAAC;AACL,CAAC;AAED,SAAS,MAAM,CACb,EAAM,EACN,OAAgB,EAChB,MAA6F;IAE7F,IAAI,CAAC,OAAO,CAAC,MAAM;QAAE,OAAO,IAAI,CAAC,EAAE,EAAE,MAAM,CAAC,CAAC;IAC7C,MAAM,EAAE,GAAG,OAAO,MAAM,CAAC,IAAI,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,eAAe,CAAC;IACnF,MAAM,MAAM,GAAG,MAAM,CAAC,QAAQ,CAAC,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,WAAW,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC;IAC3F,OAAO,QAAQ,CAAC,EAAE,EAAE,GAAG,EAAE,MAAM,MAAM,CAAC,IAAI,GAAG,MAAM,MAAM,MAAM,CAAC,KAAK,IAAI,EAAE,EAAE,CAAC,CAAC;AACjF,CAAC"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { type ExitCode, type Io } from "../output.js";
|
|
2
|
+
import { type Session } from "../session.js";
|
|
3
|
+
/**
|
|
4
|
+
* `pharos detach <id> <url-or-guid>` — take a file off a work item.
|
|
5
|
+
*
|
|
6
|
+
* ## The gap this closes
|
|
7
|
+
*
|
|
8
|
+
* The CLI could attach a file from the beginning and could never un-attach one,
|
|
9
|
+
* so a mistake was permanent from a shell. The macOS app gained the verb in
|
|
10
|
+
* #642; this is the same operation.
|
|
11
|
+
*
|
|
12
|
+
* ## The trap, which `unlink` already solves
|
|
13
|
+
*
|
|
14
|
+
* **An attachment IS a relation**, so removing one is `op remove` on
|
|
15
|
+
* `/relations/{index}` — and Azure DevOps removes BY POSITION. An index worked
|
|
16
|
+
* out from a stale read cuts whatever has since moved into that slot, which
|
|
17
|
+
* could be a parent link or an artifact link, and the request still succeeds.
|
|
18
|
+
*
|
|
19
|
+
* So this reuses exactly what `unlink` uses: read the item, find the relation by
|
|
20
|
+
* identity, and send the removal under a `test` op on the revision it was found
|
|
21
|
+
* in. If the attachment is not there it REFUSES (exit 3) rather than guessing.
|
|
22
|
+
*
|
|
23
|
+
* An `AttachedFile` is a **resource** link — it has no target work item id — so
|
|
24
|
+
* it is matched on its URL. That is why the argument is the url `pharos task`
|
|
25
|
+
* prints, or the bare GUID it ends with.
|
|
26
|
+
*
|
|
27
|
+
* **The bytes are not deleted.** This removes the file FROM THE ITEM; the
|
|
28
|
+
* attachment itself stays where it was uploaded. That is Azure DevOps'
|
|
29
|
+
* behaviour, not a choice made here — and it is why this is recoverable by
|
|
30
|
+
* re-attaching the same url, which is also why it needs `--yes` and not a
|
|
31
|
+
* paragraph of warning.
|
|
32
|
+
*/
|
|
33
|
+
export declare function runDetach(io: Io, session: Session, positionals: readonly string[]): Promise<ExitCode>;
|
|
34
|
+
//# sourceMappingURL=detach.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"detach.d.ts","sourceRoot":"","sources":["../../src/commands/detach.ts"],"names":[],"mappings":"AAOA,OAAO,EAAuC,KAAK,QAAQ,EAAE,KAAK,EAAE,EAAE,MAAM,cAAc,CAAC;AAC3F,OAAO,EAAW,KAAK,OAAO,EAAE,MAAM,eAAe,CAAC;AAGtD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAsB,SAAS,CAC7B,EAAE,EAAE,EAAE,EACN,OAAO,EAAE,OAAO,EAChB,WAAW,EAAE,SAAS,MAAM,EAAE,GAC7B,OAAO,CAAC,QAAQ,CAAC,CAwDnB"}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { RelationNotFoundError, removeRelationPatch, } from "@floh-solutions/ado-core";
|
|
2
|
+
import { emit, emitText, refusal, usageError } from "../output.js";
|
|
3
|
+
import { approve } from "../session.js";
|
|
4
|
+
import { workItemId } from "./update.js";
|
|
5
|
+
/**
|
|
6
|
+
* `pharos detach <id> <url-or-guid>` — take a file off a work item.
|
|
7
|
+
*
|
|
8
|
+
* ## The gap this closes
|
|
9
|
+
*
|
|
10
|
+
* The CLI could attach a file from the beginning and could never un-attach one,
|
|
11
|
+
* so a mistake was permanent from a shell. The macOS app gained the verb in
|
|
12
|
+
* #642; this is the same operation.
|
|
13
|
+
*
|
|
14
|
+
* ## The trap, which `unlink` already solves
|
|
15
|
+
*
|
|
16
|
+
* **An attachment IS a relation**, so removing one is `op remove` on
|
|
17
|
+
* `/relations/{index}` — and Azure DevOps removes BY POSITION. An index worked
|
|
18
|
+
* out from a stale read cuts whatever has since moved into that slot, which
|
|
19
|
+
* could be a parent link or an artifact link, and the request still succeeds.
|
|
20
|
+
*
|
|
21
|
+
* So this reuses exactly what `unlink` uses: read the item, find the relation by
|
|
22
|
+
* identity, and send the removal under a `test` op on the revision it was found
|
|
23
|
+
* in. If the attachment is not there it REFUSES (exit 3) rather than guessing.
|
|
24
|
+
*
|
|
25
|
+
* An `AttachedFile` is a **resource** link — it has no target work item id — so
|
|
26
|
+
* it is matched on its URL. That is why the argument is the url `pharos task`
|
|
27
|
+
* prints, or the bare GUID it ends with.
|
|
28
|
+
*
|
|
29
|
+
* **The bytes are not deleted.** This removes the file FROM THE ITEM; the
|
|
30
|
+
* attachment itself stays where it was uploaded. That is Azure DevOps'
|
|
31
|
+
* behaviour, not a choice made here — and it is why this is recoverable by
|
|
32
|
+
* re-attaching the same url, which is also why it needs `--yes` and not a
|
|
33
|
+
* paragraph of warning.
|
|
34
|
+
*/
|
|
35
|
+
export async function runDetach(io, session, positionals) {
|
|
36
|
+
const id = workItemId(positionals[0], "Which work item? `pharos detach <id> <attachment>`.");
|
|
37
|
+
const given = positionals[1];
|
|
38
|
+
if (given === undefined) {
|
|
39
|
+
throw usageError(`Which attachment? \`pharos detach ${id} <url-or-guid>\` — `
|
|
40
|
+
+ "`pharos task <id>` prints the url of every one.");
|
|
41
|
+
}
|
|
42
|
+
const item = await session.client.workItems.get(id, { expand: "relations" });
|
|
43
|
+
const relations = relationsOf(item);
|
|
44
|
+
const found = matchAttachment(relations, given);
|
|
45
|
+
if (found === undefined) {
|
|
46
|
+
throw refusal(`Work item ${id} has no attachment matching "${given}". `
|
|
47
|
+
+ "Nothing was removed — removing by position instead of by identity would have cut a "
|
|
48
|
+
+ "different link and reported success.", { id, given, attachments: attachmentSummary(relations) });
|
|
49
|
+
}
|
|
50
|
+
const decision = approve(io, session, {
|
|
51
|
+
what: `detach "${found.name}" from work item ${id}`,
|
|
52
|
+
would: {
|
|
53
|
+
id,
|
|
54
|
+
name: found.name,
|
|
55
|
+
url: found.url,
|
|
56
|
+
// Said in the preview because it is the thing somebody is deciding about:
|
|
57
|
+
// this takes the file OFF the item and does not delete it.
|
|
58
|
+
note: "The attachment itself stays in Azure DevOps — re-attaching the same url puts it back.",
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
if (decision !== undefined)
|
|
62
|
+
return decision;
|
|
63
|
+
let ops;
|
|
64
|
+
try {
|
|
65
|
+
ops = removeRelationPatch(relations, item.rev, { kind: "resource", url: found.url });
|
|
66
|
+
}
|
|
67
|
+
catch (error) {
|
|
68
|
+
if (error instanceof RelationNotFoundError) {
|
|
69
|
+
throw refusal(error.message, { id, given });
|
|
70
|
+
}
|
|
71
|
+
throw error;
|
|
72
|
+
}
|
|
73
|
+
await session.client.workItems.update(id, ops, { suppressNotifications: true });
|
|
74
|
+
if (session.pretty) {
|
|
75
|
+
emitText(io, `Removed ${found.name} from work item ${id}.`);
|
|
76
|
+
return 0;
|
|
77
|
+
}
|
|
78
|
+
emit(io, { applied: true, removed: true, id, name: found.name, url: found.url });
|
|
79
|
+
return 0;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Which attachment the argument names.
|
|
83
|
+
*
|
|
84
|
+
* The whole url or the bare GUID, because `pharos task` prints the url and a
|
|
85
|
+
* person reading it copies the last segment as often as the whole thing. Matched
|
|
86
|
+
* case-insensitively on the GUID: it is a GUID, and Azure DevOps is not
|
|
87
|
+
* consistent about its case between endpoints.
|
|
88
|
+
*/
|
|
89
|
+
function matchAttachment(relations, given) {
|
|
90
|
+
const wanted = given.trim().toLowerCase();
|
|
91
|
+
const guid = wanted.split("?")[0].split("/").pop() ?? wanted;
|
|
92
|
+
for (const relation of relations) {
|
|
93
|
+
if (relation.rel !== "AttachedFile")
|
|
94
|
+
continue;
|
|
95
|
+
const url = relation.url ?? "";
|
|
96
|
+
const theirGuid = url.split("?")[0].split("/").pop()?.toLowerCase() ?? "";
|
|
97
|
+
if (url.toLowerCase() === wanted || (guid !== "" && theirGuid === guid)) {
|
|
98
|
+
const name = relation.attributes?.["name"] ?? theirGuid;
|
|
99
|
+
return { url, name };
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return undefined;
|
|
103
|
+
}
|
|
104
|
+
/** What IS attached, so a refusal can be acted on rather than only read. */
|
|
105
|
+
function attachmentSummary(relations) {
|
|
106
|
+
return relations
|
|
107
|
+
.filter((relation) => relation.rel === "AttachedFile")
|
|
108
|
+
.map((relation) => ({
|
|
109
|
+
name: relation.attributes?.["name"] ?? "",
|
|
110
|
+
url: relation.url ?? "",
|
|
111
|
+
}));
|
|
112
|
+
}
|
|
113
|
+
function relationsOf(item) {
|
|
114
|
+
const relations = item.relations;
|
|
115
|
+
return relations ?? [];
|
|
116
|
+
}
|
|
117
|
+
//# sourceMappingURL=detach.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"detach.js","sourceRoot":"","sources":["../../src/commands/detach.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { type ExitCode, type Io } from "../output.js";
|
|
2
|
+
import type { Session } from "../session.js";
|
|
3
|
+
/**
|
|
4
|
+
* The three verbs that stop a session guessing: `download`, `types`, `whoami`.
|
|
5
|
+
*
|
|
6
|
+
* Grouped because they share a reason rather than an endpoint. Each one exists
|
|
7
|
+
* because its absence made a session **retry** — ask, be refused, re-read the
|
|
8
|
+
* error, ask again — and a retry loop is the most expensive thing a CLI can do
|
|
9
|
+
* to an agent, because it looks like progress.
|
|
10
|
+
*
|
|
11
|
+
* They also pass the scope test this repo now applies to every new verb (#590):
|
|
12
|
+
* a verb earns its place by adding a guard, a composition, or the agent
|
|
13
|
+
* contract, never by existing. `az devops` has no attachment command at all, no
|
|
14
|
+
* work-item-type discovery command, and no who-am-I. None of these is a
|
|
15
|
+
* re-implementation of something that already ships.
|
|
16
|
+
*/
|
|
17
|
+
export interface DownloadCommandOptions {
|
|
18
|
+
out: string | undefined;
|
|
19
|
+
name: string | undefined;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* `pharos download <id-or-url>` — read an attachment back.
|
|
23
|
+
*
|
|
24
|
+
* **This closes a hole we opened ourselves.** `pharos attach` could put a file
|
|
25
|
+
* on a work item and nothing could take one off: the Azure DevOps MCP server's
|
|
26
|
+
* attachment tool was download-only, and removing that server in #585 removed
|
|
27
|
+
* the one thing it uniquely did. `ado-core` had `attachments.download` the whole
|
|
28
|
+
* time.
|
|
29
|
+
*
|
|
30
|
+
* It takes the **url or the GUID** on purpose. `pharos task` prints
|
|
31
|
+
* `attachments[].url`, so a session that has just read an item is holding a url
|
|
32
|
+
* — making it extract a GUID from that is asking it to parse, and parsing is
|
|
33
|
+
* where a session invents a wrong answer.
|
|
34
|
+
*/
|
|
35
|
+
export declare function runDownload(io: Io, session: Session, cwd: string, positionals: readonly string[], options: DownloadCommandOptions): Promise<ExitCode>;
|
|
36
|
+
export interface TypesCommandOptions {
|
|
37
|
+
type: string | undefined;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* `pharos types` — what `--state` and `--type` will actually accept.
|
|
41
|
+
*
|
|
42
|
+
* **The single biggest retry cause in this CLI.** `pharos update 225 --state
|
|
43
|
+
* Doing` fails if that work item type has no `Doing`, and a session with no way
|
|
44
|
+
* to ask simply guesses again. The data was already being fetched — the state
|
|
45
|
+
* catalogue was built for `query`'s openness in #582 and is memoised on the
|
|
46
|
+
* client — and nothing exposed it.
|
|
47
|
+
*
|
|
48
|
+
* The categories are shown, not just the names, because *"which of these means
|
|
49
|
+
* finished"* is a different question from *"which of these exist"*, and the
|
|
50
|
+
* second one alone is what makes somebody hard-code `Done`.
|
|
51
|
+
*/
|
|
52
|
+
export declare function runTypes(io: Io, session: Session, options: TypesCommandOptions): Promise<ExitCode>;
|
|
53
|
+
/**
|
|
54
|
+
* `pharos whoami` — who `ADO_PAT` actually belongs to.
|
|
55
|
+
*
|
|
56
|
+
* Worth a verb because the answer is not always the person reading it. `@Me` in
|
|
57
|
+
* a WIQL query resolves server-side against the token owner, so a **shared or
|
|
58
|
+
* service token quietly makes "assigned to me" mean somebody else** — and
|
|
59
|
+
* nothing else in the CLI will ever mention it.
|
|
60
|
+
*
|
|
61
|
+
* It is also the cheapest possible "is this token alive": one GET, org-scoped,
|
|
62
|
+
* so it answers even when `ADO_PROJECT` is wrong — which is exactly the
|
|
63
|
+
* situation where somebody is trying to work out what is misconfigured.
|
|
64
|
+
*/
|
|
65
|
+
export declare function runWhoami(io: Io, session: Session): Promise<ExitCode>;
|
|
66
|
+
//# sourceMappingURL=discover.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"discover.d.ts","sourceRoot":"","sources":["../../src/commands/discover.ts"],"names":[],"mappings":"AAKA,OAAO,EAA8B,KAAK,QAAQ,EAAE,KAAK,EAAE,EAAE,MAAM,cAAc,CAAC;AAClF,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAE7C;;;;;;;;;;;;;GAaG;AAIH,MAAM,WAAW,sBAAsB;IACrC,GAAG,EAAE,MAAM,GAAG,SAAS,CAAC;IACxB,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC;CAC1B;AAKD;;;;;;;;;;;;;GAaG;AACH,wBAAsB,WAAW,CAC/B,EAAE,EAAE,EAAE,EACN,OAAO,EAAE,OAAO,EAChB,GAAG,EAAE,MAAM,EACX,WAAW,EAAE,SAAS,MAAM,EAAE,EAC9B,OAAO,EAAE,sBAAsB,GAC9B,OAAO,CAAC,QAAQ,CAAC,CAuCnB;AAID,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC;CAC1B;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,QAAQ,CAC5B,EAAE,EAAE,EAAE,EACN,OAAO,EAAE,OAAO,EAChB,OAAO,EAAE,mBAAmB,GAC3B,OAAO,CAAC,QAAQ,CAAC,CAuCnB;AAID;;;;;;;;;;;GAWG;AACH,wBAAsB,SAAS,CAAC,EAAE,EAAE,EAAE,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,CAiB3E"}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { writeFile } from "node:fs/promises";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
import { authenticatedIdentity } from "@floh-solutions/ado-core";
|
|
4
|
+
import { emit, emitText, usageError } from "../output.js";
|
|
5
|
+
/** `wit/attachments/{guid}` — the tail of what `pharos task` prints. */
|
|
6
|
+
const ATTACHMENT_GUID = /attachments\/([0-9a-f-]{36})/i;
|
|
7
|
+
/**
|
|
8
|
+
* `pharos download <id-or-url>` — read an attachment back.
|
|
9
|
+
*
|
|
10
|
+
* **This closes a hole we opened ourselves.** `pharos attach` could put a file
|
|
11
|
+
* on a work item and nothing could take one off: the Azure DevOps MCP server's
|
|
12
|
+
* attachment tool was download-only, and removing that server in #585 removed
|
|
13
|
+
* the one thing it uniquely did. `ado-core` had `attachments.download` the whole
|
|
14
|
+
* time.
|
|
15
|
+
*
|
|
16
|
+
* It takes the **url or the GUID** on purpose. `pharos task` prints
|
|
17
|
+
* `attachments[].url`, so a session that has just read an item is holding a url
|
|
18
|
+
* — making it extract a GUID from that is asking it to parse, and parsing is
|
|
19
|
+
* where a session invents a wrong answer.
|
|
20
|
+
*/
|
|
21
|
+
export async function runDownload(io, session, cwd, positionals, options) {
|
|
22
|
+
const given = positionals[0];
|
|
23
|
+
if (given === undefined) {
|
|
24
|
+
throw usageError("Which attachment? `pharos download <id-or-url>` — the url is what `pharos task` prints "
|
|
25
|
+
+ "under attachments.");
|
|
26
|
+
}
|
|
27
|
+
const id = ATTACHMENT_GUID.exec(given)?.[1] ?? given;
|
|
28
|
+
if (!/^[0-9a-f-]{36}$/i.test(id)) {
|
|
29
|
+
throw usageError(`"${given}" is neither an attachment GUID nor a url containing one.`, { given, expected: "a GUID, or .../_apis/wit/attachments/<guid>" });
|
|
30
|
+
}
|
|
31
|
+
const bytes = await session.client.attachments.download(id, {
|
|
32
|
+
...(options.name === undefined ? {} : { fileName: options.name }),
|
|
33
|
+
});
|
|
34
|
+
// No `--out` writes nothing and says how big it is. Printing binary to stdout
|
|
35
|
+
// would corrupt the JSON contract every other verb keeps, and an agent that
|
|
36
|
+
// piped it somewhere would get a mangled file rather than an error.
|
|
37
|
+
if (options.out === undefined) {
|
|
38
|
+
return emit(io, {
|
|
39
|
+
attachmentId: id,
|
|
40
|
+
bytes: bytes.length,
|
|
41
|
+
saved: null,
|
|
42
|
+
hint: "Pass --out <path> to write it. Nothing is printed to stdout: these are raw bytes.",
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
const path = resolve(cwd, options.out);
|
|
46
|
+
await writeFile(path, bytes);
|
|
47
|
+
return session.pretty
|
|
48
|
+
? emitText(io, `${bytes.length} bytes → ${path}`)
|
|
49
|
+
: emit(io, { attachmentId: id, bytes: bytes.length, saved: path });
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* `pharos types` — what `--state` and `--type` will actually accept.
|
|
53
|
+
*
|
|
54
|
+
* **The single biggest retry cause in this CLI.** `pharos update 225 --state
|
|
55
|
+
* Doing` fails if that work item type has no `Doing`, and a session with no way
|
|
56
|
+
* to ask simply guesses again. The data was already being fetched — the state
|
|
57
|
+
* catalogue was built for `query`'s openness in #582 and is memoised on the
|
|
58
|
+
* client — and nothing exposed it.
|
|
59
|
+
*
|
|
60
|
+
* The categories are shown, not just the names, because *"which of these means
|
|
61
|
+
* finished"* is a different question from *"which of these exist"*, and the
|
|
62
|
+
* second one alone is what makes somebody hard-code `Done`.
|
|
63
|
+
*/
|
|
64
|
+
export async function runTypes(io, session, options) {
|
|
65
|
+
const catalog = await session.client.workItemTypes.states();
|
|
66
|
+
const wanted = options.type;
|
|
67
|
+
const entries = [...catalog.byType]
|
|
68
|
+
.filter(([name]) => wanted === undefined || name.toLowerCase() === wanted.toLowerCase())
|
|
69
|
+
.map(([name, states]) => ({ type: name, open: states.open, terminal: states.terminal }));
|
|
70
|
+
if (wanted !== undefined && entries.length === 0) {
|
|
71
|
+
throw usageError(`No work item type called "${wanted}" in this project.`, {
|
|
72
|
+
types: [...catalog.byType.keys()],
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
const result = {
|
|
76
|
+
types: entries,
|
|
77
|
+
// The flat view, which is what a --state value is checked against.
|
|
78
|
+
open: catalog.open,
|
|
79
|
+
terminal: catalog.terminal,
|
|
80
|
+
ambiguous: catalog.ambiguous,
|
|
81
|
+
...(catalog.ambiguous.length === 0
|
|
82
|
+
? {}
|
|
83
|
+
: {
|
|
84
|
+
note: `${catalog.ambiguous.join(", ")} mean different things on different types, so a state `
|
|
85
|
+
+ "name alone does not say whether an item is finished.",
|
|
86
|
+
}),
|
|
87
|
+
};
|
|
88
|
+
if (!session.pretty)
|
|
89
|
+
return emit(io, result);
|
|
90
|
+
const lines = [];
|
|
91
|
+
for (const entry of entries) {
|
|
92
|
+
lines.push(entry.type);
|
|
93
|
+
lines.push(` open: ${entry.open.join(", ") || "—"}`);
|
|
94
|
+
lines.push(` finished: ${entry.terminal.join(", ") || "—"}`);
|
|
95
|
+
}
|
|
96
|
+
if (catalog.ambiguous.length > 0)
|
|
97
|
+
lines.push("", `ambiguous: ${catalog.ambiguous.join(", ")}`);
|
|
98
|
+
return emitText(io, lines.join("\n"));
|
|
99
|
+
}
|
|
100
|
+
// MARK: - whoami
|
|
101
|
+
/**
|
|
102
|
+
* `pharos whoami` — who `ADO_PAT` actually belongs to.
|
|
103
|
+
*
|
|
104
|
+
* Worth a verb because the answer is not always the person reading it. `@Me` in
|
|
105
|
+
* a WIQL query resolves server-side against the token owner, so a **shared or
|
|
106
|
+
* service token quietly makes "assigned to me" mean somebody else** — and
|
|
107
|
+
* nothing else in the CLI will ever mention it.
|
|
108
|
+
*
|
|
109
|
+
* It is also the cheapest possible "is this token alive": one GET, org-scoped,
|
|
110
|
+
* so it answers even when `ADO_PROJECT` is wrong — which is exactly the
|
|
111
|
+
* situation where somebody is trying to work out what is misconfigured.
|
|
112
|
+
*/
|
|
113
|
+
export async function runWhoami(io, session) {
|
|
114
|
+
const me = await authenticatedIdentity(session.client.http);
|
|
115
|
+
const result = {
|
|
116
|
+
id: me.id,
|
|
117
|
+
displayName: me.displayName,
|
|
118
|
+
uniqueName: me.uniqueName ?? null,
|
|
119
|
+
organization: session.client.organization,
|
|
120
|
+
project: session.client.project ?? null,
|
|
121
|
+
note: "This is who ADO_PAT belongs to. @Me in a query resolves to this identity, and every write "
|
|
122
|
+
+ "is attributed to it.",
|
|
123
|
+
};
|
|
124
|
+
return session.pretty
|
|
125
|
+
? emitText(io, `${me.displayName}${me.uniqueName === undefined ? "" : ` <${me.uniqueName}>`}`)
|
|
126
|
+
: emit(io, result);
|
|
127
|
+
}
|
|
128
|
+
//# sourceMappingURL=discover.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"discover.js","sourceRoot":"","sources":["../../src/commands/discover.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC7C,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,OAAO,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AAEjE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,UAAU,EAA0B,MAAM,cAAc,CAAC;AAyBlF,wEAAwE;AACxE,MAAM,eAAe,GAAG,+BAA+B,CAAC;AAExD;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,EAAM,EACN,OAAgB,EAChB,GAAW,EACX,WAA8B,EAC9B,OAA+B;IAE/B,MAAM,KAAK,GAAG,WAAW,CAAC,CAAC,CAAC,CAAC;IAC7B,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;QACxB,MAAM,UAAU,CACd,yFAAyF;cACrF,oBAAoB,CACzB,CAAC;IACJ,CAAC;IAED,MAAM,EAAE,GAAG,eAAe,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC;IACrD,IAAI,CAAC,kBAAkB,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC;QACjC,MAAM,UAAU,CACd,IAAI,KAAK,2DAA2D,EACpE,EAAE,KAAK,EAAE,QAAQ,EAAE,6CAA6C,EAAE,CACnE,CAAC;IACJ,CAAC;IAED,MAAM,KAAK,GAAG,MAAM,OAAO,CAAC,MAAM,CAAC,WAAW,CAAC,QAAQ,CAAC,EAAE,EAAE;QAC1D,GAAG,CAAC,OAAO,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,OAAO,CAAC,IAAI,EAAE,CAAC;KAClE,CAAC,CAAC;IAEH,8EAA8E;IAC9E,4EAA4E;IAC5E,oEAAoE;IACpE,IAAI,OAAO,CAAC,GAAG,KAAK,SAAS,EAAE,CAAC;QAC9B,OAAO,IAAI,CAAC,EAAE,EAAE;YACd,YAAY,EAAE,EAAE;YAChB,KAAK,EAAE,KAAK,CAAC,MAAM;YACnB,KAAK,EAAE,IAAI;YACX,IAAI,EAAE,mFAAmF;SAC1F,CAAC,CAAC;IACL,CAAC;IAED,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC;IACvC,MAAM,SAAS,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IAE7B,OAAO,OAAO,CAAC,MAAM;QACnB,CAAC,CAAC,QAAQ,CAAC,EAAE,EAAE,GAAG,KAAK,CAAC,MAAM,YAAY,IAAI,EAAE,CAAC;QACjD,CAAC,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,YAAY,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,CAAC,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;AACvE,CAAC;AAQD;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,KAAK,UAAU,QAAQ,CAC5B,EAAM,EACN,OAAgB,EAChB,OAA4B;IAE5B,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,MAAM,CAAC,aAAa,CAAC,MAAM,EAAE,CAAC;IAE5D,MAAM,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAC5B,MAAM,OAAO,GAAG,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC;SAChC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC,MAAM,KAAK,SAAS,IAAI,IAAI,CAAC,WAAW,EAAE,KAAK,MAAM,CAAC,WAAW,EAAE,CAAC;SACvF,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,QAAQ,EAAE,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC;IAE3F,IAAI,MAAM,KAAK,SAAS,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACjD,MAAM,UAAU,CAAC,6BAA6B,MAAM,oBAAoB,EAAE;YACxE,KAAK,EAAE,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;SAClC,CAAC,CAAC;IACL,CAAC;IAED,MAAM,MAAM,GAAG;QACb,KAAK,EAAE,OAAO;QACd,mEAAmE;QACnE,IAAI,EAAE,OAAO,CAAC,IAAI;QAClB,QAAQ,EAAE,OAAO,CAAC,QAAQ;QAC1B,SAAS,EAAE,OAAO,CAAC,SAAS;QAC5B,GAAG,CAAC,OAAO,CAAC,SAAS,CAAC,MAAM,KAAK,CAAC;YAChC,CAAC,CAAC,EAAE;YACJ,CAAC,CAAC;gBACE,IAAI,EACF,GAAG,OAAO,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,wDAAwD;sBACrF,sDAAsD;aAC3D,CAAC;KACP,CAAC;IAEF,IAAI,CAAC,OAAO,CAAC,MAAM;QAAE,OAAO,IAAI,CAAC,EAAE,EAAE,MAAM,CAAC,CAAC;IAE7C,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC5B,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QACvB,KAAK,CAAC,IAAI,CAAC,eAAe,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,GAAG,EAAE,CAAC,CAAC;QAC1D,KAAK,CAAC,IAAI,CAAC,eAAe,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,GAAG,EAAE,CAAC,CAAC;IAChE,CAAC;IACD,IAAI,OAAO,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC;QAAE,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,cAAc,OAAO,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAC/F,OAAO,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;AACxC,CAAC;AAED,iBAAiB;AAEjB;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,EAAM,EAAE,OAAgB;IACtD,MAAM,EAAE,GAAG,MAAM,qBAAqB,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IAE5D,MAAM,MAAM,GAAG;QACb,EAAE,EAAE,EAAE,CAAC,EAAE;QACT,WAAW,EAAE,EAAE,CAAC,WAAW;QAC3B,UAAU,EAAE,EAAE,CAAC,UAAU,IAAI,IAAI;QACjC,YAAY,EAAE,OAAO,CAAC,MAAM,CAAC,YAAY;QACzC,OAAO,EAAE,OAAO,CAAC,MAAM,CAAC,OAAO,IAAI,IAAI;QACvC,IAAI,EACF,4FAA4F;cAC1F,sBAAsB;KAC3B,CAAC;IAEF,OAAO,OAAO,CAAC,MAAM;QACnB,CAAC,CAAC,QAAQ,CAAC,EAAE,EAAE,GAAG,EAAE,CAAC,WAAW,GAAG,EAAE,CAAC,UAAU,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,EAAE,CAAC,UAAU,GAAG,EAAE,CAAC;QAC9F,CAAC,CAAC,IAAI,CAAC,EAAE,EAAE,MAAM,CAAC,CAAC;AACvB,CAAC"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { type ExitCode, type Io } from "../output.js";
|
|
2
|
+
import { type Session } from "../session.js";
|
|
3
|
+
/**
|
|
4
|
+
* `pharos hooks …` — the service hook subscriptions that make realtime work.
|
|
5
|
+
*
|
|
6
|
+
* These are the one piece of setup nobody is told about. Without them realtime
|
|
7
|
+
* silently does not happen and the app reports "synced 4s ago", which is true,
|
|
8
|
+
* because polling carries on. You have to already know the feature exists to
|
|
9
|
+
* notice it is missing — so the first job of this command is simply to make the
|
|
10
|
+
* state visible.
|
|
11
|
+
*
|
|
12
|
+
* There is no MCP tool for any of this, and the wiki subscription **cannot be
|
|
13
|
+
* created in the Azure DevOps web UI at all**: the repository picker hides wiki
|
|
14
|
+
* repositories, so the only options a browser offers are `[Any]` or the code
|
|
15
|
+
* repo. This is the only correct route.
|
|
16
|
+
*/
|
|
17
|
+
interface HooksInput {
|
|
18
|
+
hub?: string | undefined;
|
|
19
|
+
user?: string | undefined;
|
|
20
|
+
password?: string | undefined;
|
|
21
|
+
}
|
|
22
|
+
export declare function runHooks(io: Io, session: Session, positionals: readonly string[], input: HooksInput): Promise<ExitCode>;
|
|
23
|
+
export {};
|
|
24
|
+
//# sourceMappingURL=hooks.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../../src/commands/hooks.ts"],"names":[],"mappings":"AAOA,OAAO,EAAoB,KAAK,QAAQ,EAAE,KAAK,EAAE,EAAE,MAAM,cAAc,CAAC;AACxE,OAAO,EAAW,KAAK,OAAO,EAAE,MAAM,eAAe,CAAC;AAEtD;;;;;;;;;;;;;GAaG;AAEH,UAAU,UAAU;IAClB,GAAG,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACzB,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC/B;AAED,wBAAsB,QAAQ,CAC5B,EAAE,EAAE,EAAE,EACN,OAAO,EAAE,OAAO,EAChB,WAAW,EAAE,SAAS,MAAM,EAAE,EAC9B,KAAK,EAAE,UAAU,GAChB,OAAO,CAAC,QAAQ,CAAC,CAmBnB"}
|