@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 +148 -0
- package/dist/bin.d.ts +3 -0
- package/dist/bin.d.ts.map +1 -0
- package/dist/bin.js +19 -0
- package/dist/bin.js.map +1 -0
- package/dist/budget.d.ts +43 -0
- package/dist/budget.d.ts.map +1 -0
- package/dist/budget.js +92 -0
- package/dist/budget.js.map +1 -0
- package/dist/cli.d.ts +15 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +249 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/comment.d.ts +6 -0
- package/dist/commands/comment.d.ts.map +1 -0
- package/dist/commands/comment.js +236 -0
- package/dist/commands/comment.js.map +1 -0
- package/dist/commands/task.d.ts +23 -0
- package/dist/commands/task.d.ts.map +1 -0
- package/dist/commands/task.js +112 -0
- package/dist/commands/task.js.map +1 -0
- package/dist/commands/wiki.d.ts +20 -0
- package/dist/commands/wiki.d.ts.map +1 -0
- package/dist/commands/wiki.js +163 -0
- package/dist/commands/wiki.js.map +1 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -0
- package/dist/output.d.ts +87 -0
- package/dist/output.d.ts.map +1 -0
- package/dist/output.js +194 -0
- package/dist/output.js.map +1 -0
- package/dist/session.d.ts +77 -0
- package/dist/session.d.ts.map +1 -0
- package/dist/session.js +114 -0
- package/dist/session.js.map +1 -0
- package/dist/target.d.ts +25 -0
- package/dist/target.d.ts.map +1 -0
- package/dist/target.js +23 -0
- package/dist/target.js.map +1 -0
- package/package.json +42 -0
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 @@
|
|
|
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
|
package/dist/bin.js.map
ADDED
|
@@ -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"}
|
package/dist/budget.d.ts
ADDED
|
@@ -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
|
package/dist/cli.js.map
ADDED
|
@@ -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"}
|