@floh-solutions/pharos-cli 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,148 @@
1
+ # `pharos`
2
+
3
+ Azure DevOps from a headless shell, built for an agent to drive.
4
+
5
+ ```bash
6
+ npm i -g @floh-solutions/pharos-cli
7
+ export ADO_ORG=your-org ADO_PROJECT=YourProject ADO_PAT=…
8
+ pharos task 210
9
+ ```
10
+
11
+ ## Why it exists
12
+
13
+ Microsoft's `@azure-devops/mcp` server is missing capabilities that matter once
14
+ Claude is doing real work on a board. Enumerated against its shipped tool list,
15
+ not assumed:
16
+
17
+ | | |
18
+ |---|---|
19
+ | wiki comments — list, add, edit, delete, react | **no tool at all** |
20
+ | work item comments — delete, react/unreact | absent (it has add and update only) |
21
+ | wiki page delete | absent |
22
+ | service hook subscriptions | absent |
23
+
24
+ And it authenticates with an **interactive browser login**, so an unattended
25
+ agent blocks on a modal nobody is there to answer.
26
+
27
+ ## The command it exists for
28
+
29
+ ```bash
30
+ pharos task 210
31
+ ```
32
+
33
+ One call returns the work item, its fields, its comments, its attachments, its
34
+ relations **with their titles**, and the full content of every linked wiki page
35
+ **plus the discussion on those pages**.
36
+
37
+ Assembled by hand that is five or six round trips across two different tools,
38
+ and one of them is impossible. It is what an agent needs in order to *start*:
39
+ the plan a colleague wrote on a wiki page, the argument underneath it, and the
40
+ dependency that says this cannot begin yet. Anything forgotten is context the
41
+ agent silently works without.
42
+
43
+ It gathers; it does not summarise or rank. Whatever could not be fetched lands
44
+ in `problems[]` rather than being quietly dropped — a context with an invisible
45
+ hole in it is worse than a short one, because it gets reasoned from
46
+ confidently.
47
+
48
+ Add `--pretty` for a human-readable brief.
49
+
50
+ ## The output is the interface
51
+
52
+ **Success is JSON on stdout. Failure is JSON on stderr with a non-zero exit.**
53
+ An empty array and exit 0 is a query that matched nothing; that is a different
54
+ fact from a 403, and prose makes it a guess.
55
+
56
+ | exit | meaning | what to do |
57
+ |---|---|---|
58
+ | `0` | it worked | carry on |
59
+ | `1` | the call failed | maybe retry — check `kind`, and `retryAfterMs` if rate-limited |
60
+ | `2` | called wrong, or not configured | **never** retry unchanged |
61
+ | `3` | refused by a guard here | re-run with `--yes`, or raise `--max-writes` |
62
+
63
+ Errors carry what you need to act:
64
+
65
+ ```json
66
+ { "error": { "kind": "conflict", "expectedRev": 4, "actualRev": 5,
67
+ "hint": "Re-read the item and re-apply…" } }
68
+ ```
69
+
70
+ A bad token exits **2**, not 1 — retrying it is pure noise. A conflict carries
71
+ both revisions, and says out loud that posting a comment bumps `System.Rev`, so
72
+ a rev mismatch is *not* proof that somebody edited the same field.
73
+
74
+ ## Guards
75
+
76
+ Every verb here is one an agent can call in a loop against a board real people
77
+ depend on.
78
+
79
+ **Destructive verbs refuse by default.**
80
+
81
+ ```bash
82
+ pharos wiki delete /Plans/Old # exit 3, nothing changed, preview attached
83
+ pharos wiki delete /Plans/Old --dry-run # exit 0 — the preview IS the request
84
+ pharos wiki delete /Plans/Old --yes # done
85
+ ```
86
+
87
+ The bare form exits **non-zero on purpose**. Exiting 0 with `"applied": false`
88
+ reads as success to anything that checks only the status, and an agent that
89
+ quietly does less looks identical to one that succeeded. `--dry-run` beats
90
+ `--yes`: the safe reading of a contradiction is the one that changes nothing.
91
+
92
+ **Replacing a wiki page needs `--yes`; creating one does not.** Creating adds,
93
+ replacing can destroy somebody's text. The read that decides which is also
94
+ where the `If-Match` version comes from, so the concurrency guard is not
95
+ something a caller can skip.
96
+
97
+ **A write budget, counted at the transport.** Default 20 per invocation, so a
98
+ runaway loop becomes a clean refusal rather than 500 work items. `--max-writes 0`
99
+ is read-only: reads work, every write is refused before it is sent.
100
+
101
+ It is **not security** — anything that can run this holds the token and can make
102
+ the same REST calls directly. It shapes intent and catches accidents. A
103
+ capability that must be impossible has to be withheld at the token.
104
+
105
+ ## Commands
106
+
107
+ ```
108
+ task <id> the whole context, in one call
109
+ wiki list | tree | read | write | delete
110
+ comment list | add | edit | delete | react | unreact | reactors
111
+ plan <file> an implementation plan becomes a work item tree
112
+ ```
113
+
114
+ `comment` takes either kind of target: a work item is its number (`210`), a wiki
115
+ page is an **absolute path** (`/Plans/Foo`). The leading slash is required by
116
+ the wiki API itself — it answers 404 without one — so the spelling that tells
117
+ them apart is also the only spelling that works.
118
+
119
+ Long text does not belong on a command line:
120
+
121
+ ```bash
122
+ pharos comment add 210 --file review.md
123
+ pharos wiki write /Plans/Sprint-4 --stdin < plan.md
124
+ ```
125
+
126
+ ## Configuration
127
+
128
+ | | |
129
+ |---|---|
130
+ | `ADO_ORG` | your organisation — the first path component of your `dev.azure.com` URL |
131
+ | `ADO_PROJECT` | the project inside it |
132
+ | `ADO_PAT` | a Personal Access Token |
133
+
134
+ **From the environment, and nowhere else.** No keychain, no credential helper,
135
+ no config file — that is what lets this run headless, in CI, or under an agent
136
+ with nobody there to answer a prompt. There is deliberately no built-in
137
+ organisation either: a default would let a misconfigured run succeed quietly
138
+ against somebody else's board.
139
+
140
+ Scopes are separate and both are needed for full use: `vso.work_write` for work
141
+ items, `vso.wiki_write` for the wiki.
142
+
143
+ **Every person uses their own token.** Board attribution is per-person, and a
144
+ shared token makes everyone's work read as one account.
145
+
146
+ ## Requirements
147
+
148
+ Node 22 or later.
package/dist/bin.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=bin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bin.d.ts","sourceRoot":"","sources":["../src/bin.ts"],"names":[],"mappings":""}
package/dist/bin.js ADDED
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/env node
2
+ import { run } from "./cli.js";
3
+ const code = await run({
4
+ argv: process.argv.slice(2),
5
+ cwd: process.cwd(),
6
+ env: process.env,
7
+ stdout: (line) => process.stdout.write(`${line}\n`),
8
+ stderr: (line) => process.stderr.write(`${line}\n`),
9
+ }).catch((error) => {
10
+ // `run` maps everything it can into the structured error contract; this is
11
+ // only reached if the mapping itself broke. Keep the shape anyway, so a
12
+ // consumer never has to parse two different things off stderr.
13
+ process.stderr.write(`${JSON.stringify({
14
+ error: { kind: "internal", message: error instanceof Error ? error.message : String(error) },
15
+ })}\n`);
16
+ return 1;
17
+ });
18
+ process.exitCode = code;
19
+ //# sourceMappingURL=bin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bin.js","sourceRoot":"","sources":["../src/bin.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AAE/B,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC;IACrB,IAAI,EAAE,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;IAC3B,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE;IAClB,GAAG,EAAE,OAAO,CAAC,GAAG;IAChB,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,IAAI,IAAI,CAAC;IACnD,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,IAAI,IAAI,CAAC;CACpD,CAAC,CAAC,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE;IAC1B,2EAA2E;IAC3E,wEAAwE;IACxE,+DAA+D;IAC/D,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,GAAG,IAAI,CAAC,SAAS,CAAC;QAChB,KAAK,EAAE,EAAE,IAAI,EAAE,UAAU,EAAE,OAAO,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE;KAC7F,CAAC,IAAI,CACP,CAAC;IACF,OAAO,CAAC,CAAC;AACX,CAAC,CAAC,CAAC;AAEH,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC"}
@@ -0,0 +1,43 @@
1
+ import type { FetchLike } from "@floh-solutions/ado-core";
2
+ /**
3
+ * A cap on how many writes one invocation may perform.
4
+ *
5
+ * ## Why it is counted at the transport and not at the call sites
6
+ *
7
+ * An agent in a loop can create five hundred work items before anybody notices.
8
+ * The cap is what turns that from an incident into a refusal — so the one thing
9
+ * it must not be is *optional to remember*. Counting inside each command means
10
+ * every new command is a chance to forget, and the command that forgets is the
11
+ * one nobody tested against a runaway.
12
+ *
13
+ * Counting requests as they leave means a write cannot avoid the budget by not
14
+ * declaring itself, including writes made by code this package merely calls —
15
+ * `plan-to-board` creates a whole work item tree and knows nothing about any of
16
+ * this.
17
+ *
18
+ * ## What it is not
19
+ *
20
+ * **It is not security.** Anything that can run this CLI holds the PAT and can
21
+ * make the same REST calls directly. This shapes intent and catches accidents,
22
+ * which is most of the real value, but a capability that must be *impossible*
23
+ * has to be withheld at the token — see `docs/pharos-cli-plan.md` §2.5.
24
+ */
25
+ /** Deliberately small. A single invocation doing twenty writes is unusual. */
26
+ export declare const DEFAULT_WRITE_BUDGET = 20;
27
+ export declare function isWriteRequest(method: string, url: string): boolean;
28
+ export declare class WriteBudget {
29
+ #private;
30
+ readonly limit: number;
31
+ constructor(limit?: number);
32
+ get spent(): number;
33
+ get remaining(): number;
34
+ /**
35
+ * Wrap a fetch so every write is counted, and the one that would exceed the
36
+ * cap is **refused before it is sent** rather than reported after.
37
+ *
38
+ * A retried write counts once per attempt, deliberately: a run that is
39
+ * retrying hard is exactly when a cap should bite sooner rather than later.
40
+ */
41
+ wrap(inner: FetchLike): FetchLike;
42
+ }
43
+ //# sourceMappingURL=budget.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"budget.d.ts","sourceRoot":"","sources":["../src/budget.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAI1D;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,8EAA8E;AAC9E,eAAO,MAAM,oBAAoB,KAAK,CAAC;AAuBvC,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAInE;AAED,qBAAa,WAAW;;IACtB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;gBAGX,KAAK,GAAE,MAA6B;IAIhD,IAAI,KAAK,IAAI,MAAM,CAElB;IAED,IAAI,SAAS,IAAI,MAAM,CAEtB;IAED;;;;;;OAMG;IACH,IAAI,CAAC,KAAK,EAAE,SAAS,GAAG,SAAS;CAiBlC"}
package/dist/budget.js ADDED
@@ -0,0 +1,92 @@
1
+ import { refusal } from "./output.js";
2
+ /**
3
+ * A cap on how many writes one invocation may perform.
4
+ *
5
+ * ## Why it is counted at the transport and not at the call sites
6
+ *
7
+ * An agent in a loop can create five hundred work items before anybody notices.
8
+ * The cap is what turns that from an incident into a refusal — so the one thing
9
+ * it must not be is *optional to remember*. Counting inside each command means
10
+ * every new command is a chance to forget, and the command that forgets is the
11
+ * one nobody tested against a runaway.
12
+ *
13
+ * Counting requests as they leave means a write cannot avoid the budget by not
14
+ * declaring itself, including writes made by code this package merely calls —
15
+ * `plan-to-board` creates a whole work item tree and knows nothing about any of
16
+ * this.
17
+ *
18
+ * ## What it is not
19
+ *
20
+ * **It is not security.** Anything that can run this CLI holds the PAT and can
21
+ * make the same REST calls directly. This shapes intent and catches accidents,
22
+ * which is most of the real value, but a capability that must be *impossible*
23
+ * has to be withheld at the token — see `docs/pharos-cli-plan.md` §2.5.
24
+ */
25
+ /** Deliberately small. A single invocation doing twenty writes is unusual. */
26
+ export const DEFAULT_WRITE_BUDGET = 20;
27
+ /**
28
+ * POSTs that are reads.
29
+ *
30
+ * Azure DevOps expresses two of its most common *reads* as POSTs, because the
31
+ * query does not fit in a URL. Counting them as writes would mean
32
+ * `pharos task 210` — which writes nothing at all — burning budget, and a
33
+ * read-only invocation failing with a write-cap error is the kind of nonsense
34
+ * that teaches people to raise the cap to infinity.
35
+ *
36
+ * **Adding a read-shaped POST endpoint means adding it here.** The failure is
37
+ * not silent, at least: it shows up as a read that spends budget.
38
+ */
39
+ const READ_SHAPED_POSTS = [
40
+ "/_apis/wit/workitemsbatch",
41
+ "/_apis/wit/wiql",
42
+ // Page ids in bulk. Documented as page-VIEW statistics and shaped like a
43
+ // write; it is the only route to a wiki page's numeric id short of one
44
+ // request per page, and `pharos wiki tree` reads it every time.
45
+ "/pagesbatch",
46
+ ];
47
+ export function isWriteRequest(method, url) {
48
+ if (method.toUpperCase() === "GET")
49
+ return false;
50
+ const path = url.toLowerCase();
51
+ return !READ_SHAPED_POSTS.some((endpoint) => path.includes(endpoint));
52
+ }
53
+ export class WriteBudget {
54
+ limit;
55
+ #spent = 0;
56
+ constructor(limit = DEFAULT_WRITE_BUDGET) {
57
+ this.limit = limit;
58
+ }
59
+ get spent() {
60
+ return this.#spent;
61
+ }
62
+ get remaining() {
63
+ return Math.max(0, this.limit - this.#spent);
64
+ }
65
+ /**
66
+ * Wrap a fetch so every write is counted, and the one that would exceed the
67
+ * cap is **refused before it is sent** rather than reported after.
68
+ *
69
+ * A retried write counts once per attempt, deliberately: a run that is
70
+ * retrying hard is exactly when a cap should bite sooner rather than later.
71
+ */
72
+ wrap(inner) {
73
+ return async (input, init) => {
74
+ const method = init?.method ?? "GET";
75
+ if (isWriteRequest(method, input)) {
76
+ if (this.#spent >= this.limit) {
77
+ throw refusal(`Write budget of ${this.limit} exhausted; refusing to send ${method} ${redact(input)}. `
78
+ + "Raise it with --max-writes if this run genuinely needs more, but check first that "
79
+ + "it is not looping.", { limit: this.limit, spent: this.#spent, method, url: redact(input) });
80
+ }
81
+ this.#spent += 1;
82
+ }
83
+ return inner(input, init);
84
+ };
85
+ }
86
+ }
87
+ /** Query strings can carry a continuation token; the path is the useful part. */
88
+ function redact(url) {
89
+ const cut = url.indexOf("?");
90
+ return cut === -1 ? url : url.slice(0, cut);
91
+ }
92
+ //# sourceMappingURL=budget.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"budget.js","sourceRoot":"","sources":["../src/budget.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAEtC;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,8EAA8E;AAC9E,MAAM,CAAC,MAAM,oBAAoB,GAAG,EAAE,CAAC;AAEvC;;;;;;;;;;;GAWG;AACH,MAAM,iBAAiB,GAAG;IACxB,2BAA2B;IAC3B,iBAAiB;IACjB,yEAAyE;IACzE,uEAAuE;IACvE,gEAAgE;IAChE,aAAa;CACd,CAAC;AAEF,MAAM,UAAU,cAAc,CAAC,MAAc,EAAE,GAAW;IACxD,IAAI,MAAM,CAAC,WAAW,EAAE,KAAK,KAAK;QAAE,OAAO,KAAK,CAAC;IACjD,MAAM,IAAI,GAAG,GAAG,CAAC,WAAW,EAAE,CAAC;IAC/B,OAAO,CAAC,iBAAiB,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC;AACxE,CAAC;AAED,MAAM,OAAO,WAAW;IACb,KAAK,CAAS;IACvB,MAAM,GAAG,CAAC,CAAC;IAEX,YAAY,QAAgB,oBAAoB;QAC9C,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;IACrB,CAAC;IAED,IAAI,KAAK;QACP,OAAO,IAAI,CAAC,MAAM,CAAC;IACrB,CAAC;IAED,IAAI,SAAS;QACX,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC;IAC/C,CAAC;IAED;;;;;;OAMG;IACH,IAAI,CAAC,KAAgB;QACnB,OAAO,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE;YAC3B,MAAM,MAAM,GAAG,IAAI,EAAE,MAAM,IAAI,KAAK,CAAC;YACrC,IAAI,cAAc,CAAC,MAAM,EAAE,KAAK,CAAC,EAAE,CAAC;gBAClC,IAAI,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;oBAC9B,MAAM,OAAO,CACX,mBAAmB,IAAI,CAAC,KAAK,gCAAgC,MAAM,IAAI,MAAM,CAAC,KAAK,CAAC,IAAI;0BACpF,oFAAoF;0BACpF,oBAAoB,EACxB,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,KAAK,EAAE,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,CAAC,KAAK,CAAC,EAAE,CACtE,CAAC;gBACJ,CAAC;gBACD,IAAI,CAAC,MAAM,IAAI,CAAC,CAAC;YACnB,CAAC;YACD,OAAO,KAAK,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;QAC5B,CAAC,CAAC;IACJ,CAAC;CACF;AAED,iFAAiF;AACjF,SAAS,MAAM,CAAC,GAAW;IACzB,MAAM,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IAC7B,OAAO,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;AAC9C,CAAC"}
package/dist/cli.d.ts ADDED
@@ -0,0 +1,15 @@
1
+ import type { AdoClient } from "@floh-solutions/ado-core";
2
+ import { type ExitCode } from "./output.js";
3
+ export interface RunOptions {
4
+ argv: string[];
5
+ cwd: string;
6
+ env: NodeJS.ProcessEnv;
7
+ stdout: (line: string) => void;
8
+ stderr: (line: string) => void;
9
+ /** Injected by tests. Production reads fd 0. */
10
+ readStdin?: (() => Promise<string>) | undefined;
11
+ /** Injected by tests, so the whole router can run against a mock transport. */
12
+ client?: AdoClient | undefined;
13
+ }
14
+ export declare function run(options: RunOptions): Promise<ExitCode>;
15
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAO1D,OAAO,EAKL,KAAK,QAAQ,EAEd,MAAM,aAAa,CAAC;AA4FrB,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC,UAAU,CAAC;IACvB,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/B,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/B,gDAAgD;IAChD,SAAS,CAAC,EAAE,CAAC,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC,GAAG,SAAS,CAAC;IAChD,+EAA+E;IAC/E,MAAM,CAAC,EAAE,SAAS,GAAG,SAAS,CAAC;CAChC;AAED,wBAAsB,GAAG,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,QAAQ,CAAC,CAOhE"}
package/dist/cli.js ADDED
@@ -0,0 +1,249 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import { resolve } from "node:path";
3
+ import { parseArgs } from "node:util";
4
+ import { run as runPlanToBoard } from "@floh-solutions/plan-to-board";
5
+ import { DEFAULT_WRITE_BUDGET, WriteBudget } from "./budget.js";
6
+ import { runComment } from "./commands/comment.js";
7
+ import { runTask } from "./commands/task.js";
8
+ import { runWiki } from "./commands/wiki.js";
9
+ import { EXIT_OK, emitText, reportError, usageError, } from "./output.js";
10
+ import { Session } from "./session.js";
11
+ /**
12
+ * Flags every command understands.
13
+ *
14
+ * `--json` is accepted and does nothing: JSON *is* the default output, and the
15
+ * flag exists because it is the obvious thing to reach for. Erroring on it
16
+ * would fail a call that asked for exactly what it was already getting.
17
+ */
18
+ const GLOBAL_OPTIONS = {
19
+ pretty: { type: "boolean" },
20
+ json: { type: "boolean" },
21
+ yes: { type: "boolean", short: "y" },
22
+ "dry-run": { type: "boolean" },
23
+ "max-writes": { type: "string" },
24
+ org: { type: "string" },
25
+ project: { type: "string" },
26
+ wiki: { type: "string" },
27
+ help: { type: "boolean", short: "h" },
28
+ version: { type: "boolean" },
29
+ };
30
+ /** Where long text comes from, for the verbs that take some. */
31
+ const CONTENT_OPTIONS = {
32
+ text: { type: "string" },
33
+ file: { type: "string" },
34
+ stdin: { type: "boolean" },
35
+ };
36
+ const USAGE = `pharos — Azure DevOps from a headless shell, for an agent.
37
+
38
+ pharos <command> [options]
39
+
40
+ COMMANDS
41
+
42
+ task <id> Everything about one work item in a single call:
43
+ fields, comments, attachments, relations with
44
+ their titles, and the CONTENT of any linked wiki
45
+ pages plus the discussion on them. This is the
46
+ command the rest exists to support.
47
+ --no-wiki-content do not fetch linked page content
48
+ --no-wiki-comments do not fetch comments on linked pages
49
+ --no-related-titles ids only; skip the batch that names them
50
+
51
+ wiki list Every wiki, with the ids links and hooks need
52
+ wiki tree The page tree, WITH page ids (two calls, joined)
53
+ wiki read <path> Content plus the version a write must carry
54
+ wiki write <path> Create, or replace (replacing needs --yes)
55
+ wiki delete <path> Needs --yes
56
+
57
+ comment list <target> <target> is a work item id (210) or a wiki page
58
+ comment add <target> [text] path (/Plans/Foo). Wiki comments are reachable
59
+ comment edit <target> <id> [text] through no other tool at all.
60
+ comment delete <target> <id> Needs --yes
61
+ comment react <target> <id> <type> like dislike heart hooray smile confused
62
+ comment unreact <target> <id> <type>
63
+ comment reactors <target> <id> <type> who reacted, by name
64
+
65
+ plan <file> [...] Turn an implementation plan into a work item
66
+ tree. Passed through to plan-to-board verbatim,
67
+ including its flags and its human output.
68
+
69
+ TEXT INPUT
70
+ --text <string> for anything longer than a shell argument
71
+ --file <path> read it from a file
72
+ --stdin read it from the pipe
73
+
74
+ GLOBAL
75
+ --pretty human output. Default is JSON on stdout.
76
+ --yes, -y apply destructive changes. Without it they are
77
+ REFUSED (exit 3), not silently skipped.
78
+ --dry-run show what would happen and exit 0. Beats --yes.
79
+ --max-writes <n> per-invocation write cap (default ${DEFAULT_WRITE_BUDGET}).
80
+ 0 means read-only: reads work, writes refused.
81
+ --org <name> or ADO_ORG
82
+ --project <name> or ADO_PROJECT
83
+ --wiki <name> only needed when the project has several
84
+ --version, --help, -h
85
+
86
+ EXIT CODES
87
+ 0 it worked 2 called wrong, or not configured — do not retry
88
+ 1 the call failed 3 refused by a guard here — re-run with --yes
89
+
90
+ AUTH
91
+ ADO_PAT from the environment. Nothing else, and never the Keychain: this has
92
+ to work headless, which is the entire point. Work item writes need the
93
+ vso.work_write scope; wiki writes need vso.wiki_write, separately.
94
+ `;
95
+ export async function run(options) {
96
+ const io = { stdout: options.stdout, stderr: options.stderr };
97
+ try {
98
+ return await dispatch(io, options);
99
+ }
100
+ catch (error) {
101
+ return reportError(io, error);
102
+ }
103
+ }
104
+ async function dispatch(io, options) {
105
+ const command = options.argv.find((argument) => !argument.startsWith("-"));
106
+ // `plan` is a passthrough and must not have its flags eaten here — it has its
107
+ // own, they collide (`--wiki` means something different there), and the whole
108
+ // point of a passthrough is that the delegate keeps its interface.
109
+ if (command === "plan") {
110
+ return plan(options);
111
+ }
112
+ const extras = command === "task"
113
+ ? {
114
+ "no-wiki-content": { type: "boolean" },
115
+ "no-wiki-comments": { type: "boolean" },
116
+ "no-related-titles": { type: "boolean" },
117
+ }
118
+ : CONTENT_OPTIONS;
119
+ const { values, positionals } = parse(options.argv, { ...GLOBAL_OPTIONS, ...extras });
120
+ if (values["version"] === true)
121
+ return emitText(io, await version(options.cwd));
122
+ if (values["help"] === true || command === undefined)
123
+ return emitText(io, USAGE);
124
+ const session = new Session({
125
+ env: options.env,
126
+ organization: asString(values["org"]),
127
+ project: asString(values["project"]),
128
+ wiki: asString(values["wiki"]),
129
+ budget: new WriteBudget(maxWrites(values["max-writes"])),
130
+ // --dry-run beats --yes on purpose: the safe reading of a contradiction is
131
+ // the one that changes nothing.
132
+ apply: values["yes"] === true && values["dry-run"] !== true,
133
+ previewOnly: values["dry-run"] === true,
134
+ pretty: values["pretty"] === true,
135
+ client: options.client,
136
+ });
137
+ const rest = positionals.slice(1);
138
+ switch (command) {
139
+ case "task":
140
+ return runTask(io, session, rest, {
141
+ wikiContent: values["no-wiki-content"] !== true,
142
+ wikiComments: values["no-wiki-comments"] !== true,
143
+ relatedTitles: values["no-related-titles"] !== true,
144
+ });
145
+ case "wiki":
146
+ return runWiki(io, session, rest, {
147
+ content: await content(options, values),
148
+ });
149
+ case "comment":
150
+ return runComment(io, session, rest, {
151
+ text: await content(options, values),
152
+ });
153
+ default:
154
+ throw usageError(`Unknown command "${command}". Try task, wiki, comment or plan.`, {
155
+ commands: ["task", "wiki", "comment", "plan"],
156
+ });
157
+ }
158
+ }
159
+ /**
160
+ * `plan` hands the whole tail to `plan-to-board` untouched.
161
+ *
162
+ * **Its output is human text, not JSON**, which is the one exception to this
163
+ * CLI's output contract. Wrapping it would mean either re-implementing its
164
+ * rendering or emitting a string inside a JSON envelope that no consumer wants;
165
+ * folding it in properly is worth doing when something actually needs to parse
166
+ * a plan run, and pretending it already is JSON would be worse than saying so.
167
+ */
168
+ async function plan(options) {
169
+ const index = options.argv.indexOf("plan");
170
+ const code = await runPlanToBoard({
171
+ argv: options.argv.slice(index + 1),
172
+ cwd: options.cwd,
173
+ env: options.env,
174
+ stdout: options.stdout,
175
+ stderr: options.stderr,
176
+ ...(options.client === undefined ? {} : { client: options.client }),
177
+ });
178
+ return code === 0 ? EXIT_OK : code;
179
+ }
180
+ function parse(argv, optionsConfig) {
181
+ try {
182
+ const parsed = parseArgs({
183
+ args: [...argv],
184
+ options: optionsConfig,
185
+ allowPositionals: true,
186
+ strict: true,
187
+ });
188
+ return { values: parsed.values, positionals: parsed.positionals };
189
+ }
190
+ catch (error) {
191
+ // A typo'd flag must not be ignored: an agent that passed --dryrun and got
192
+ // a real write would have no way to know that is what happened.
193
+ throw usageError(error instanceof Error ? error.message : String(error));
194
+ }
195
+ }
196
+ /** Long text, from a flag, a file, or the pipe. Exactly one source. */
197
+ async function content(options, values) {
198
+ const sources = ["text", "file", "stdin"].filter((key) => values[key] !== undefined);
199
+ if (sources.length > 1) {
200
+ throw usageError(`Pass only one of --text, --file or --stdin; got ${sources.join(" and ")}.`);
201
+ }
202
+ const text = asString(values["text"]);
203
+ if (text !== undefined)
204
+ return text;
205
+ const file = asString(values["file"]);
206
+ if (file !== undefined)
207
+ return readFile(resolve(options.cwd, file), "utf8");
208
+ if (values["stdin"] === true) {
209
+ return (options.readStdin ?? readAllStdin)();
210
+ }
211
+ return undefined;
212
+ }
213
+ async function readAllStdin() {
214
+ const chunks = [];
215
+ for await (const chunk of process.stdin)
216
+ chunks.push(Buffer.from(chunk));
217
+ return Buffer.concat(chunks).toString("utf8");
218
+ }
219
+ /**
220
+ * `--max-writes 0` is allowed and means **read-only**: every write is refused,
221
+ * every read still works. Worth having as more than a test hook — it is the one
222
+ * setting that makes "this invocation cannot change anything" a property of the
223
+ * call rather than a promise about which verbs were used.
224
+ */
225
+ function maxWrites(raw) {
226
+ if (typeof raw !== "string")
227
+ return DEFAULT_WRITE_BUDGET;
228
+ const value = Number(raw);
229
+ if (!Number.isInteger(value) || value < 0) {
230
+ throw usageError(`--max-writes takes a whole number of 0 or more, not "${raw}". 0 means read-only.`);
231
+ }
232
+ return value;
233
+ }
234
+ async function version(cwd) {
235
+ try {
236
+ const manifest = await readFile(new URL("../package.json", import.meta.url), "utf8");
237
+ const parsed = JSON.parse(manifest);
238
+ return parsed.version ?? "unknown";
239
+ }
240
+ catch {
241
+ // Running from a checkout laid out differently. Not worth failing over.
242
+ void cwd;
243
+ return "unknown";
244
+ }
245
+ }
246
+ function asString(value) {
247
+ return typeof value === "string" && value !== "" ? value : undefined;
248
+ }
249
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ import { type ExitCode, type Io } from "../output.js";
2
+ import { type Session } from "../session.js";
3
+ export declare function runComment(io: Io, session: Session, positionals: readonly string[], input: {
4
+ text?: string | undefined;
5
+ }): Promise<ExitCode>;
6
+ //# sourceMappingURL=comment.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"comment.d.ts","sourceRoot":"","sources":["../../src/commands/comment.ts"],"names":[],"mappings":"AAEA,OAAO,EAAoB,KAAK,QAAQ,EAAE,KAAK,EAAE,EAAE,MAAM,cAAc,CAAC;AACxE,OAAO,EAAW,KAAK,OAAO,EAAE,MAAM,eAAe,CAAC;AAoCtD,wBAAsB,UAAU,CAC9B,EAAE,EAAE,EAAE,EACN,OAAO,EAAE,OAAO,EAChB,WAAW,EAAE,SAAS,MAAM,EAAE,EAC9B,KAAK,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,GACnC,OAAO,CAAC,QAAQ,CAAC,CAoCnB"}