@floh-solutions/pharos-cli 0.1.1 → 0.3.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 +38 -3
- package/dist/cli.js.map +1 -1
- package/dist/commands/setup.d.ts +16 -0
- package/dist/commands/setup.d.ts.map +1 -0
- package/dist/commands/setup.js +337 -0
- package/dist/commands/setup.js.map +1 -0
- package/package.json +5 -4
- package/skill/SKILL.md +138 -0
package/dist/cli.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAQ1D,OAAO,EAKL,KAAK,QAAQ,EAEd,MAAM,aAAa,CAAC;AA6GrB,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
CHANGED
|
@@ -4,6 +4,7 @@ import { parseArgs } from "node:util";
|
|
|
4
4
|
import { run as runPlanToBoard } from "@floh-solutions/plan-to-board";
|
|
5
5
|
import { DEFAULT_WRITE_BUDGET, WriteBudget } from "./budget.js";
|
|
6
6
|
import { runComment } from "./commands/comment.js";
|
|
7
|
+
import { runSetup } from "./commands/setup.js";
|
|
7
8
|
import { runTask } from "./commands/task.js";
|
|
8
9
|
import { runWiki } from "./commands/wiki.js";
|
|
9
10
|
import { EXIT_OK, emitText, reportError, usageError, } from "./output.js";
|
|
@@ -37,6 +38,23 @@ const USAGE = `pharos — Azure DevOps from a headless shell, for an agent.
|
|
|
37
38
|
|
|
38
39
|
pharos <command> [options]
|
|
39
40
|
|
|
41
|
+
START HERE
|
|
42
|
+
|
|
43
|
+
setup Everything needed to start, in one command.
|
|
44
|
+
Asks for your organisation, project and token;
|
|
45
|
+
stores the token in your OS keychain rather
|
|
46
|
+
than a file; writes the shell profile that
|
|
47
|
+
SCRIPTED shells read, not just interactive
|
|
48
|
+
ones; installs a Claude skill at user scope so
|
|
49
|
+
every session knows this tool is here; then
|
|
50
|
+
proves it by reading the board and the wiki.
|
|
51
|
+
Those are separate Azure DevOps permissions,
|
|
52
|
+
and a token missing the wiki one works fine
|
|
53
|
+
until the first wiki write days later.
|
|
54
|
+
--print show the shell block and change nothing
|
|
55
|
+
--stdin read the token from a pipe, for scripted setup
|
|
56
|
+
--no-skill do not install the Claude skill
|
|
57
|
+
|
|
40
58
|
COMMANDS
|
|
41
59
|
|
|
42
60
|
task <id> Everything about one work item in a single call:
|
|
@@ -115,12 +133,29 @@ async function dispatch(io, options) {
|
|
|
115
133
|
"no-wiki-comments": { type: "boolean" },
|
|
116
134
|
"no-related-titles": { type: "boolean" },
|
|
117
135
|
}
|
|
118
|
-
:
|
|
136
|
+
: command === "setup"
|
|
137
|
+
? {
|
|
138
|
+
print: { type: "boolean" },
|
|
139
|
+
stdin: { type: "boolean" },
|
|
140
|
+
"no-skill": { type: "boolean" },
|
|
141
|
+
}
|
|
142
|
+
: CONTENT_OPTIONS;
|
|
119
143
|
const { values, positionals } = parse(options.argv, { ...GLOBAL_OPTIONS, ...extras });
|
|
120
144
|
if (values["version"] === true)
|
|
121
145
|
return emitText(io, await version(options.cwd));
|
|
122
146
|
if (values["help"] === true || command === undefined)
|
|
123
147
|
return emitText(io, USAGE);
|
|
148
|
+
// Before the Session: setup is what you run when there is no configuration
|
|
149
|
+
// yet, so it must not be behind the thing that requires configuration.
|
|
150
|
+
if (command === "setup") {
|
|
151
|
+
return runSetup(io, options.env, {
|
|
152
|
+
org: asString(values["org"]),
|
|
153
|
+
project: asString(values["project"]),
|
|
154
|
+
print: values["print"] === true,
|
|
155
|
+
stdin: values["stdin"] === true,
|
|
156
|
+
skill: values["no-skill"] !== true,
|
|
157
|
+
}, options.readStdin ?? readAllStdin, options.client);
|
|
158
|
+
}
|
|
124
159
|
const session = new Session({
|
|
125
160
|
env: options.env,
|
|
126
161
|
organization: asString(values["org"]),
|
|
@@ -151,8 +186,8 @@ async function dispatch(io, options) {
|
|
|
151
186
|
text: await content(options, values),
|
|
152
187
|
});
|
|
153
188
|
default:
|
|
154
|
-
throw usageError(`Unknown command "${command}". Try task, wiki, comment or plan.`, {
|
|
155
|
-
commands: ["task", "wiki", "comment", "plan"],
|
|
189
|
+
throw usageError(`Unknown command "${command}". Try setup, task, wiki, comment or plan.`, {
|
|
190
|
+
commands: ["setup", "task", "wiki", "comment", "plan"],
|
|
156
191
|
});
|
|
157
192
|
}
|
|
158
193
|
}
|
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,SAAS,EAAwB,MAAM,WAAW,CAAC;AAG5D,OAAO,EAAE,GAAG,IAAI,cAAc,EAAE,MAAM,+BAA+B,CAAC;AAEtE,OAAO,EAAE,oBAAoB,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAChE,OAAO,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AACnD,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAC7C,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAC7C,OAAO,EACL,OAAO,EACP,QAAQ,EACR,WAAW,EACX,UAAU,GAGX,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAIvC;;;;;;GAMG;AACH,MAAM,cAAc,GAAkB;IACpC,MAAM,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE;IAC3B,IAAI,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE;IACzB,GAAG,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,GAAG,EAAE;IACpC,SAAS,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE;IAC9B,YAAY,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;IAChC,GAAG,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;IACvB,OAAO,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;IAC3B,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;IACxB,IAAI,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,GAAG,EAAE;IACrC,OAAO,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE;CAC7B,CAAC;AAEF,gEAAgE;AAChE,MAAM,eAAe,GAAkB;IACrC,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;IACxB,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;IACxB,KAAK,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE;CAC3B,CAAC;AAEF,MAAM,KAAK,GAAG
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { AdoClient } from "@floh-solutions/ado-core";
|
|
2
|
+
import { type ExitCode, type Io } from "../output.js";
|
|
3
|
+
export interface SetupOptions {
|
|
4
|
+
org?: string | undefined;
|
|
5
|
+
project?: string | undefined;
|
|
6
|
+
/** Print the shell block and change nothing. For people who manage dotfiles themselves. */
|
|
7
|
+
print: boolean;
|
|
8
|
+
/** Read the token from stdin instead of prompting — for scripted setup. */
|
|
9
|
+
stdin: boolean;
|
|
10
|
+
/** Install the Claude skill. Default true; `--no-skill` opts out. */
|
|
11
|
+
skill: boolean;
|
|
12
|
+
}
|
|
13
|
+
export declare function runSetup(io: Io, env: NodeJS.ProcessEnv, options: SetupOptions, readStdin: () => Promise<string>,
|
|
14
|
+
/** Injected so the verification can be driven against a mock transport. */
|
|
15
|
+
verifyWith?: AdoClient): Promise<ExitCode>;
|
|
16
|
+
//# sourceMappingURL=setup.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"setup.d.ts","sourceRoot":"","sources":["../../src/commands/setup.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,SAAS,EAAW,MAAM,0BAA0B,CAAC;AAE9D,OAAO,EAA8B,KAAK,QAAQ,EAAE,KAAK,EAAE,EAAE,MAAM,cAAc,CAAC;AA4ClF,MAAM,WAAW,YAAY;IAC3B,GAAG,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACzB,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC7B,2FAA2F;IAC3F,KAAK,EAAE,OAAO,CAAC;IACf,2EAA2E;IAC3E,KAAK,EAAE,OAAO,CAAC;IACf,qEAAqE;IACrE,KAAK,EAAE,OAAO,CAAC;CAChB;AAED,wBAAsB,QAAQ,CAC5B,EAAE,EAAE,EAAE,EACN,GAAG,EAAE,MAAM,CAAC,UAAU,EACtB,OAAO,EAAE,YAAY,EACrB,SAAS,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC;AAChC,2EAA2E;AAC3E,UAAU,CAAC,EAAE,SAAS,GACrB,OAAO,CAAC,QAAQ,CAAC,CAqDnB"}
|
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { chmod, mkdir, readFile, writeFile } from "node:fs/promises";
|
|
3
|
+
import { homedir, platform } from "node:os";
|
|
4
|
+
import { dirname, join } from "node:path";
|
|
5
|
+
import { createInterface } from "node:readline";
|
|
6
|
+
import { Writable } from "node:stream";
|
|
7
|
+
import { AdoClient, WikiApi } from "@floh-solutions/ado-core";
|
|
8
|
+
import { emit, emitText, usageError } from "../output.js";
|
|
9
|
+
/**
|
|
10
|
+
* `pharos setup` — get somebody from "installed" to "working" in one command.
|
|
11
|
+
*
|
|
12
|
+
* ## Why this exists at all, when the tool reads three environment variables
|
|
13
|
+
*
|
|
14
|
+
* Because "set three environment variables" is where people actually fail, and
|
|
15
|
+
* they fail *silently and late*. The three ways it goes wrong, all seen:
|
|
16
|
+
*
|
|
17
|
+
* 1. **The token ends up in a file.** Told to `export ADO_PAT=…`, the obvious
|
|
18
|
+
* move is to paste it into a dotfile, where it lives in plaintext,
|
|
19
|
+
* backs itself up, and gets committed by somebody eventually.
|
|
20
|
+
* 2. **The wrong dotfile.** zsh reads `.zshrc` only for INTERACTIVE shells,
|
|
21
|
+
* so the variables work when you type in Terminal and are invisible to
|
|
22
|
+
* anything scripted — a hook, CI, an agent shelling out. The failure is a
|
|
23
|
+
* config error that reads as a broken tool.
|
|
24
|
+
* 3. **A scope is missing.** Work Items and Wiki are separate Azure DevOps
|
|
25
|
+
* permissions. A token with only the first works perfectly until the first
|
|
26
|
+
* wiki write, days later, and then fails with a 401 that looks like a bad
|
|
27
|
+
* token rather than a narrow one.
|
|
28
|
+
*
|
|
29
|
+
* This does all three correctly, then **proves it** by making real calls.
|
|
30
|
+
*
|
|
31
|
+
* ## The boundary this does NOT cross
|
|
32
|
+
*
|
|
33
|
+
* The tool's RUNTIME still reads `ADO_PAT` from the environment and nothing
|
|
34
|
+
* else — no keychain, no config file, no credential helper. That is what keeps
|
|
35
|
+
* the same binary working in CI and under an unattended agent, and it is not
|
|
36
|
+
* negotiable.
|
|
37
|
+
*
|
|
38
|
+
* This command is a **bootstrapper**, not a credential path: run once,
|
|
39
|
+
* explicitly, interactively. It puts the token in the OS keychain and writes a
|
|
40
|
+
* shell snippet that reads it back out. So the *shell* resolves the secret and
|
|
41
|
+
* the *tool* still just inherits an environment variable — which is why running
|
|
42
|
+
* `pharos` under CI with `ADO_PAT` set works identically and never calls any of
|
|
43
|
+
* this.
|
|
44
|
+
*/
|
|
45
|
+
const BEGIN = "# >>> pharos cli >>>";
|
|
46
|
+
const END = "# <<< pharos cli <<<";
|
|
47
|
+
/** Keychain service name. Deliberately not the Pharos app's — separate tools, separate secrets. */
|
|
48
|
+
const KEYCHAIN_SERVICE = "pharos-cli-ado-pat";
|
|
49
|
+
export async function runSetup(io, env, options, readStdin,
|
|
50
|
+
/** Injected so the verification can be driven against a mock transport. */
|
|
51
|
+
verifyWith) {
|
|
52
|
+
const interactive = process.stdin.isTTY === true && !options.stdin;
|
|
53
|
+
const org = options.org ?? env["ADO_ORG"] ?? (interactive ? await ask(ORG_PROMPT) : "");
|
|
54
|
+
if (org === "")
|
|
55
|
+
throw usageError("No organisation. Pass --org, or run this in a terminal.");
|
|
56
|
+
const project = options.project ?? env["ADO_PROJECT"] ?? (interactive ? await ask(PROJECT_PROMPT) : "");
|
|
57
|
+
if (project === "")
|
|
58
|
+
throw usageError("No project. Pass --project, or run this in a terminal.");
|
|
59
|
+
const target = profileTarget(env);
|
|
60
|
+
if (options.print) {
|
|
61
|
+
return emitText(io, shellBlock(org, project, target.secret));
|
|
62
|
+
}
|
|
63
|
+
const token = options.stdin
|
|
64
|
+
? (await readStdin()).trim()
|
|
65
|
+
: interactive
|
|
66
|
+
? await askSecret(TOKEN_PROMPT)
|
|
67
|
+
: "";
|
|
68
|
+
if (token === "") {
|
|
69
|
+
throw usageError("No token. Run this in a terminal to be prompted, or pipe it in with --stdin.");
|
|
70
|
+
}
|
|
71
|
+
// Store the secret BEFORE touching the profile: a profile that references a
|
|
72
|
+
// secret which is not there yet is worse than no profile change at all.
|
|
73
|
+
await storeSecret(token, target.secret);
|
|
74
|
+
const wrote = await updateProfile(target.profile, shellBlock(org, project, target.secret));
|
|
75
|
+
const skill = options.skill ? await installSkill(env) : "skipped";
|
|
76
|
+
// Prove it, rather than declaring success and letting them find out later.
|
|
77
|
+
const checks = await verify(org, project, token, verifyWith);
|
|
78
|
+
return emit(io, {
|
|
79
|
+
applied: true,
|
|
80
|
+
organization: org,
|
|
81
|
+
project,
|
|
82
|
+
tokenStoredIn: describeSecret(target.secret),
|
|
83
|
+
profile: target.profile,
|
|
84
|
+
profileAction: wrote,
|
|
85
|
+
skill,
|
|
86
|
+
checks,
|
|
87
|
+
// The variables are exported by a file that this shell already read, so
|
|
88
|
+
// they are not in it. Saying so beats somebody concluding it failed.
|
|
89
|
+
hint: checks.workItems.ok && checks.wiki.ok
|
|
90
|
+
? `Open a new terminal, then run: pharos task <id>`
|
|
91
|
+
: "Setup wrote everything, but a scope check failed — see `checks`. Fix the token's "
|
|
92
|
+
+ "scopes in Azure DevOps and run `pharos setup` again.",
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Which file to write, and where the token lives.
|
|
97
|
+
*
|
|
98
|
+
* **zsh gets `.zshenv`, not `.zshrc`, and that is the single most important
|
|
99
|
+
* decision here.** `.zshrc` is read only by interactive shells, so a setup that
|
|
100
|
+
* lands there works in Terminal and is invisible to every scripted or agent-run
|
|
101
|
+
* invocation — which is the exact case this tool exists to serve.
|
|
102
|
+
*
|
|
103
|
+
* bash has no true equivalent. `.bashrc` is skipped by non-interactive shells
|
|
104
|
+
* too, but it is the least-bad option and the summary says so rather than
|
|
105
|
+
* implying a guarantee that is not there.
|
|
106
|
+
*/
|
|
107
|
+
function profileTarget(env) {
|
|
108
|
+
const shell = env["SHELL"] ?? "";
|
|
109
|
+
// `HOME` rather than `homedir()`: on every platform we support they agree,
|
|
110
|
+
// and reading the variable is what lets this be exercised against a scratch
|
|
111
|
+
// directory instead of somebody's actual dotfiles.
|
|
112
|
+
const home = env["HOME"] ?? homedir();
|
|
113
|
+
// `PHAROS_TOKEN_FILE` puts the token in a mode-600 file instead of the OS
|
|
114
|
+
// keychain. Not a test hook — a container, a CI image and a headless Linux
|
|
115
|
+
// box all have no keychain to speak of, and forcing one on macOS is a
|
|
116
|
+
// reasonable preference to hold.
|
|
117
|
+
const override = env["PHAROS_TOKEN_FILE"];
|
|
118
|
+
const secret = override !== undefined && override !== ""
|
|
119
|
+
? { kind: "file", path: override }
|
|
120
|
+
: platform() === "darwin"
|
|
121
|
+
? { kind: "keychain" }
|
|
122
|
+
: { kind: "file", path: join(home, ".config", "pharos", "token") };
|
|
123
|
+
if (shell.endsWith("zsh"))
|
|
124
|
+
return { profile: join(home, ".zshenv"), secret };
|
|
125
|
+
if (shell.endsWith("bash")) {
|
|
126
|
+
return {
|
|
127
|
+
profile: join(home, platform() === "darwin" ? ".bash_profile" : ".bashrc"),
|
|
128
|
+
secret,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
// Unknown shell: still write something sensible, and `--print` exists for
|
|
132
|
+
// anyone who would rather paste it themselves.
|
|
133
|
+
return { profile: join(home, ".profile"), secret };
|
|
134
|
+
}
|
|
135
|
+
function shellBlock(org, project, secret) {
|
|
136
|
+
const read = secret.kind === "keychain"
|
|
137
|
+
? `"$(security find-generic-password -s ${KEYCHAIN_SERVICE} -w 2>/dev/null)"`
|
|
138
|
+
: `"$(cat ${secret.path} 2>/dev/null)"`;
|
|
139
|
+
return [
|
|
140
|
+
BEGIN,
|
|
141
|
+
"# Written by `pharos setup`. Safe to edit; re-running setup replaces this block.",
|
|
142
|
+
"#",
|
|
143
|
+
"# The token is NOT stored here — it is read out of "
|
|
144
|
+
+ (secret.kind === "keychain" ? "your login keychain." : `${secret.path} (mode 600).`),
|
|
145
|
+
"# pharos itself only ever reads these environment variables, which is what",
|
|
146
|
+
"# lets the same command work in CI and under an unattended agent.",
|
|
147
|
+
`export ADO_ORG=${shellQuote(org)}`,
|
|
148
|
+
`export ADO_PROJECT=${shellQuote(project)}`,
|
|
149
|
+
`export ADO_PAT=${read}`,
|
|
150
|
+
END,
|
|
151
|
+
].join("\n");
|
|
152
|
+
}
|
|
153
|
+
// MARK: - The skill
|
|
154
|
+
/**
|
|
155
|
+
* Install the Claude skill to **user scope**.
|
|
156
|
+
*
|
|
157
|
+
* ## Why this ships with the tool instead of living in the repo
|
|
158
|
+
*
|
|
159
|
+
* Because the knowledge has to be somewhere the reader can actually reach, and
|
|
160
|
+
* a repo is not that place for anybody but its owner. Documentation in
|
|
161
|
+
* `AGENTS.md` or a `docs/` folder is invisible to a teammate working in their
|
|
162
|
+
* own checkout, and a setup guide is read once at install time and never again.
|
|
163
|
+
* Neither survives into the session three weeks later where the decision
|
|
164
|
+
* actually gets made.
|
|
165
|
+
*
|
|
166
|
+
* A user-scope skill does: it loads in every session, in any directory, on that
|
|
167
|
+
* person's machine. So it travels with the npm package and gets installed by
|
|
168
|
+
* the same command that configures the token — one step, not a thing to
|
|
169
|
+
* remember.
|
|
170
|
+
*
|
|
171
|
+
* **What is in it is deliberately NOT the API traps.** Those are handled inside
|
|
172
|
+
* this package and a caller cannot reach them; a second copy in prose would
|
|
173
|
+
* only be a copy that can drift. What it carries is the part no code can: which
|
|
174
|
+
* tool to reach for, what the exit codes oblige you to do, and the handful of
|
|
175
|
+
* Azure DevOps facts that are architectural rather than fixable — a wiki is a
|
|
176
|
+
* git repository, there is no wiki event, ancestors are not created for you.
|
|
177
|
+
*
|
|
178
|
+
* Overwritten on every run so an upgrade refreshes it. Failure is reported and
|
|
179
|
+
* never fatal: not having the skill is a worse experience, not a broken setup.
|
|
180
|
+
*/
|
|
181
|
+
async function installSkill(env) {
|
|
182
|
+
const home = env["HOME"] ?? homedir();
|
|
183
|
+
const destination = join(home, ".claude", "skills", "pharos", "SKILL.md");
|
|
184
|
+
try {
|
|
185
|
+
// Resolved relative to the compiled file, so it works from dist/ in a
|
|
186
|
+
// global npm install as well as from src/ in this checkout.
|
|
187
|
+
const source = new URL("../../skill/SKILL.md", import.meta.url);
|
|
188
|
+
const body = await readFile(source, "utf8");
|
|
189
|
+
await mkdir(dirname(destination), { recursive: true });
|
|
190
|
+
await writeFile(destination, body, "utf8");
|
|
191
|
+
return destination;
|
|
192
|
+
}
|
|
193
|
+
catch (error) {
|
|
194
|
+
return `not installed (${messageOf(error)})`;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
// MARK: - Doing it
|
|
198
|
+
async function storeSecret(token, secret) {
|
|
199
|
+
if (secret.kind === "keychain") {
|
|
200
|
+
// `-U` updates an existing entry rather than failing, so re-running after a
|
|
201
|
+
// token rotation is the same command.
|
|
202
|
+
await run("security", [
|
|
203
|
+
"add-generic-password",
|
|
204
|
+
"-U",
|
|
205
|
+
"-a",
|
|
206
|
+
process.env["USER"] ?? "pharos",
|
|
207
|
+
"-s",
|
|
208
|
+
KEYCHAIN_SERVICE,
|
|
209
|
+
"-w",
|
|
210
|
+
token,
|
|
211
|
+
]);
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
const path = secret.path;
|
|
215
|
+
await mkdir(dirname(path), { recursive: true, mode: 0o700 });
|
|
216
|
+
await writeFile(path, `${token}\n`, { mode: 0o600 });
|
|
217
|
+
// Explicit, because an existing file keeps its old mode through writeFile.
|
|
218
|
+
await chmod(path, 0o600);
|
|
219
|
+
}
|
|
220
|
+
/** Replace the block if it is there, append it if it is not. Never duplicate. */
|
|
221
|
+
async function updateProfile(path, block) {
|
|
222
|
+
let existing = "";
|
|
223
|
+
try {
|
|
224
|
+
existing = await readFile(path, "utf8");
|
|
225
|
+
}
|
|
226
|
+
catch {
|
|
227
|
+
await writeFile(path, `${block}\n`, { mode: 0o600 });
|
|
228
|
+
return "created";
|
|
229
|
+
}
|
|
230
|
+
const start = existing.indexOf(BEGIN);
|
|
231
|
+
const end = existing.indexOf(END);
|
|
232
|
+
if (start !== -1 && end > start) {
|
|
233
|
+
const updated = existing.slice(0, start) + block + existing.slice(end + END.length);
|
|
234
|
+
await writeFile(path, updated, "utf8");
|
|
235
|
+
return "replaced";
|
|
236
|
+
}
|
|
237
|
+
const separator = existing.endsWith("\n") ? "\n" : "\n\n";
|
|
238
|
+
await writeFile(path, `${existing}${separator}${block}\n`, "utf8");
|
|
239
|
+
return "appended";
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Check the two scopes SEPARATELY, because Azure DevOps grants them separately.
|
|
243
|
+
*
|
|
244
|
+
* A token with Work Items but not Wiki behaves perfectly until the first wiki
|
|
245
|
+
* write — possibly days later — and then fails with a 401 that looks like a bad
|
|
246
|
+
* token rather than a narrow one. Finding that out now costs one request.
|
|
247
|
+
*/
|
|
248
|
+
async function verify(org, project, pat, injected) {
|
|
249
|
+
const client = injected ?? new AdoClient({ organization: org, project, pat });
|
|
250
|
+
const workItems = await client.wiql
|
|
251
|
+
.query("SELECT [System.Id] FROM WorkItems", { top: 1 })
|
|
252
|
+
.then(() => ({ ok: true, detail: "work item read works" }))
|
|
253
|
+
.catch((error) => ({
|
|
254
|
+
ok: false,
|
|
255
|
+
detail: `work items unreadable — check the org, the project, and the token's `
|
|
256
|
+
+ `"Work Items (Read, write, & manage)" scope. ${messageOf(error)}`,
|
|
257
|
+
}));
|
|
258
|
+
const wiki = await new WikiApi(client.http)
|
|
259
|
+
.listWikis()
|
|
260
|
+
.then((wikis) => ({ ok: true, detail: `wiki read works (${wikis.length} found)` }))
|
|
261
|
+
.catch((error) => ({
|
|
262
|
+
ok: false,
|
|
263
|
+
detail: `wiki unreadable — this is a SEPARATE permission from work items; check the `
|
|
264
|
+
+ `token's "Wiki (Read & write)" scope. ${messageOf(error)}`,
|
|
265
|
+
}));
|
|
266
|
+
return { workItems, wiki };
|
|
267
|
+
}
|
|
268
|
+
// MARK: - Prompts
|
|
269
|
+
const ORG_PROMPT = "Azure DevOps organisation\n"
|
|
270
|
+
+ " (from your board's URL: https://dev.azure.com/THIS/project)\n> ";
|
|
271
|
+
const PROJECT_PROMPT = "Project\n (the next part of that URL: https://dev.azure.com/org/THIS)\n> ";
|
|
272
|
+
const TOKEN_PROMPT = "Personal access token\n"
|
|
273
|
+
+ " User settings → Personal access tokens → New Token.\n"
|
|
274
|
+
+ " Scopes, both of them: Work Items (Read, write, & manage) AND Wiki (Read & write).\n"
|
|
275
|
+
+ " It will not be echoed, and it is stored in your keychain rather than a file.\n> ";
|
|
276
|
+
async function ask(question) {
|
|
277
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
278
|
+
try {
|
|
279
|
+
return (await new Promise((resolve) => rl.question(question, resolve))).trim();
|
|
280
|
+
}
|
|
281
|
+
finally {
|
|
282
|
+
rl.close();
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Prompt without echoing.
|
|
287
|
+
*
|
|
288
|
+
* Readline is given a sink for its output rather than stdout, so the keystrokes
|
|
289
|
+
* it would normally echo go nowhere; the prompt is written directly instead.
|
|
290
|
+
* That avoids overriding readline's private `_writeToOutput`, which is the
|
|
291
|
+
* usual trick for this and breaks whenever Node tidies its internals.
|
|
292
|
+
*/
|
|
293
|
+
async function askSecret(question) {
|
|
294
|
+
process.stdout.write(question);
|
|
295
|
+
const sink = new Writable({
|
|
296
|
+
write(_chunk, _encoding, done) {
|
|
297
|
+
done();
|
|
298
|
+
},
|
|
299
|
+
});
|
|
300
|
+
const rl = createInterface({ input: process.stdin, output: sink, terminal: true });
|
|
301
|
+
try {
|
|
302
|
+
const answer = await new Promise((resolve) => rl.question("", resolve));
|
|
303
|
+
process.stdout.write("\n");
|
|
304
|
+
return answer.trim();
|
|
305
|
+
}
|
|
306
|
+
finally {
|
|
307
|
+
rl.close();
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
// MARK: - Small helpers
|
|
311
|
+
function run(command, args) {
|
|
312
|
+
return new Promise((resolve, reject) => {
|
|
313
|
+
// No shell, and the token is an argv entry rather than part of a command
|
|
314
|
+
// string — nothing to quote, nothing to interpolate, and it never reaches a
|
|
315
|
+
// shell history.
|
|
316
|
+
const child = spawn(command, args, { stdio: "ignore" });
|
|
317
|
+
child.on("error", reject);
|
|
318
|
+
child.on("close", (code) => {
|
|
319
|
+
if (code === 0)
|
|
320
|
+
resolve();
|
|
321
|
+
else
|
|
322
|
+
reject(new Error(`${command} exited ${String(code)}`));
|
|
323
|
+
});
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
function describeSecret(secret) {
|
|
327
|
+
return secret.kind === "keychain"
|
|
328
|
+
? `login keychain (service "${KEYCHAIN_SERVICE}")`
|
|
329
|
+
: `${secret.path} (mode 600)`;
|
|
330
|
+
}
|
|
331
|
+
function shellQuote(value) {
|
|
332
|
+
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
333
|
+
}
|
|
334
|
+
function messageOf(error) {
|
|
335
|
+
return error instanceof Error ? error.message : String(error);
|
|
336
|
+
}
|
|
337
|
+
//# sourceMappingURL=setup.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"setup.js","sourceRoot":"","sources":["../../src/commands/setup.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAC3C,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AACrE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAC5C,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAEvC,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,0BAA0B,CAAC;AAE9D,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,UAAU,EAA0B,MAAM,cAAc,CAAC;AAElF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AAEH,MAAM,KAAK,GAAG,sBAAsB,CAAC;AACrC,MAAM,GAAG,GAAG,sBAAsB,CAAC;AACnC,mGAAmG;AACnG,MAAM,gBAAgB,GAAG,oBAAoB,CAAC;AAa9C,MAAM,CAAC,KAAK,UAAU,QAAQ,CAC5B,EAAM,EACN,GAAsB,EACtB,OAAqB,EACrB,SAAgC;AAChC,2EAA2E;AAC3E,UAAsB;IAEtB,MAAM,WAAW,GAAG,OAAO,CAAC,KAAK,CAAC,KAAK,KAAK,IAAI,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC;IAEnE,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,IAAI,GAAG,CAAC,SAAS,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IACxF,IAAI,GAAG,KAAK,EAAE;QAAE,MAAM,UAAU,CAAC,yDAAyD,CAAC,CAAC;IAE5F,MAAM,OAAO,GACX,OAAO,CAAC,OAAO,IAAI,GAAG,CAAC,aAAa,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IAC1F,IAAI,OAAO,KAAK,EAAE;QAAE,MAAM,UAAU,CAAC,wDAAwD,CAAC,CAAC;IAE/F,MAAM,MAAM,GAAG,aAAa,CAAC,GAAG,CAAC,CAAC;IAElC,IAAI,OAAO,CAAC,KAAK,EAAE,CAAC;QAClB,OAAO,QAAQ,CAAC,EAAE,EAAE,UAAU,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC;IAC/D,CAAC;IAED,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK;QACzB,CAAC,CAAC,CAAC,MAAM,SAAS,EAAE,CAAC,CAAC,IAAI,EAAE;QAC5B,CAAC,CAAC,WAAW;YACX,CAAC,CAAC,MAAM,SAAS,CAAC,YAAY,CAAC;YAC/B,CAAC,CAAC,EAAE,CAAC;IACT,IAAI,KAAK,KAAK,EAAE,EAAE,CAAC;QACjB,MAAM,UAAU,CACd,8EAA8E,CAC/E,CAAC;IACJ,CAAC;IAED,4EAA4E;IAC5E,wEAAwE;IACxE,MAAM,WAAW,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC;IACxC,MAAM,KAAK,GAAG,MAAM,aAAa,CAAC,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC;IAC3F,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,YAAY,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IAElE,2EAA2E;IAC3E,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,GAAG,EAAE,OAAO,EAAE,KAAK,EAAE,UAAU,CAAC,CAAC;IAE7D,OAAO,IAAI,CAAC,EAAE,EAAE;QACd,OAAO,EAAE,IAAI;QACb,YAAY,EAAE,GAAG;QACjB,OAAO;QACP,aAAa,EAAE,cAAc,CAAC,MAAM,CAAC,MAAM,CAAC;QAC5C,OAAO,EAAE,MAAM,CAAC,OAAO;QACvB,aAAa,EAAE,KAAK;QACpB,KAAK;QACL,MAAM;QACN,wEAAwE;QACxE,qEAAqE;QACrE,IAAI,EACF,MAAM,CAAC,SAAS,CAAC,EAAE,IAAI,MAAM,CAAC,IAAI,CAAC,EAAE;YACnC,CAAC,CAAC,iDAAiD;YACnD,CAAC,CAAC,mFAAmF;kBACjF,sDAAsD;KAC/D,CAAC,CAAC;AACL,CAAC;AAcD;;;;;;;;;;;GAWG;AACH,SAAS,aAAa,CAAC,GAAsB;IAC3C,MAAM,KAAK,GAAG,GAAG,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC;IACjC,2EAA2E;IAC3E,4EAA4E;IAC5E,mDAAmD;IACnD,MAAM,IAAI,GAAG,GAAG,CAAC,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;IAEtC,0EAA0E;IAC1E,2EAA2E;IAC3E,sEAAsE;IACtE,iCAAiC;IACjC,MAAM,QAAQ,GAAG,GAAG,CAAC,mBAAmB,CAAC,CAAC;IAC1C,MAAM,MAAM,GACV,QAAQ,KAAK,SAAS,IAAI,QAAQ,KAAK,EAAE;QACvC,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE;QAClC,CAAC,CAAC,QAAQ,EAAE,KAAK,QAAQ;YACvB,CAAC,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE;YACtB,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,OAAO,CAAC,EAAE,CAAC;IAEzE,IAAI,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC;QAAE,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC,IAAI,EAAE,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IAC7E,IAAI,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;QAC3B,OAAO;YACL,OAAO,EAAE,IAAI,CAAC,IAAI,EAAE,QAAQ,EAAE,KAAK,QAAQ,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,SAAS,CAAC;YAC1E,MAAM;SACP,CAAC;IACJ,CAAC;IACD,0EAA0E;IAC1E,+CAA+C;IAC/C,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC,IAAI,EAAE,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;AACrD,CAAC;AAED,SAAS,UAAU,CAAC,GAAW,EAAE,OAAe,EAAE,MAAsB;IACtE,MAAM,IAAI,GACR,MAAM,CAAC,IAAI,KAAK,UAAU;QACxB,CAAC,CAAC,wCAAwC,gBAAgB,mBAAmB;QAC7E,CAAC,CAAC,UAAU,MAAM,CAAC,IAAI,gBAAgB,CAAC;IAE5C,OAAO;QACL,KAAK;QACL,kFAAkF;QAClF,GAAG;QACH,qDAAqD;cACjD,CAAC,MAAM,CAAC,IAAI,KAAK,UAAU,CAAC,CAAC,CAAC,sBAAsB,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,IAAI,cAAc,CAAC;QACxF,4EAA4E;QAC5E,mEAAmE;QACnE,kBAAkB,UAAU,CAAC,GAAG,CAAC,EAAE;QACnC,sBAAsB,UAAU,CAAC,OAAO,CAAC,EAAE;QAC3C,kBAAkB,IAAI,EAAE;QACxB,GAAG;KACJ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACf,CAAC;AAED,oBAAoB;AAEpB;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,KAAK,UAAU,YAAY,CAAC,GAAsB;IAChD,MAAM,IAAI,GAAG,GAAG,CAAC,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;IACtC,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,QAAQ,EAAE,UAAU,CAAC,CAAC;IAC1E,IAAI,CAAC;QACH,sEAAsE;QACtE,4DAA4D;QAC5D,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,sBAAsB,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAChE,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QAC5C,MAAM,KAAK,CAAC,OAAO,CAAC,WAAW,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACvD,MAAM,SAAS,CAAC,WAAW,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC;QAC3C,OAAO,WAAW,CAAC;IACrB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,kBAAkB,SAAS,CAAC,KAAK,CAAC,GAAG,CAAC;IAC/C,CAAC;AACH,CAAC;AAED,mBAAmB;AAEnB,KAAK,UAAU,WAAW,CAAC,KAAa,EAAE,MAAsB;IAC9D,IAAI,MAAM,CAAC,IAAI,KAAK,UAAU,EAAE,CAAC;QAC/B,4EAA4E;QAC5E,sCAAsC;QACtC,MAAM,GAAG,CAAC,UAAU,EAAE;YACpB,sBAAsB;YACtB,IAAI;YACJ,IAAI;YACJ,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,QAAQ;YAC/B,IAAI;YACJ,gBAAgB;YAChB,IAAI;YACJ,KAAK;SACN,CAAC,CAAC;QACH,OAAO;IACT,CAAC;IAED,MAAM,IAAI,GAAG,MAAM,CAAC,IAAK,CAAC;IAC1B,MAAM,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IAC7D,MAAM,SAAS,CAAC,IAAI,EAAE,GAAG,KAAK,IAAI,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IACrD,2EAA2E;IAC3E,MAAM,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;AAC3B,CAAC;AAED,iFAAiF;AACjF,KAAK,UAAU,aAAa,CAAC,IAAY,EAAE,KAAa;IACtD,IAAI,QAAQ,GAAG,EAAE,CAAC;IAClB,IAAI,CAAC;QACH,QAAQ,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IAC1C,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,SAAS,CAAC,IAAI,EAAE,GAAG,KAAK,IAAI,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;QACrD,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,MAAM,KAAK,GAAG,QAAQ,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;IACtC,MAAM,GAAG,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IAClC,IAAI,KAAK,KAAK,CAAC,CAAC,IAAI,GAAG,GAAG,KAAK,EAAE,CAAC;QAChC,MAAM,OAAO,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,GAAG,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC,GAAG,GAAG,GAAG,CAAC,MAAM,CAAC,CAAC;QACpF,MAAM,SAAS,CAAC,IAAI,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC;QACvC,OAAO,UAAU,CAAC;IACpB,CAAC;IAED,MAAM,SAAS,GAAG,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC;IAC1D,MAAM,SAAS,CAAC,IAAI,EAAE,GAAG,QAAQ,GAAG,SAAS,GAAG,KAAK,IAAI,EAAE,MAAM,CAAC,CAAC;IACnE,OAAO,UAAU,CAAC;AACpB,CAAC;AASD;;;;;;GAMG;AACH,KAAK,UAAU,MAAM,CACnB,GAAW,EACX,OAAe,EACf,GAAW,EACX,QAAoB;IAEpB,MAAM,MAAM,GAAG,QAAQ,IAAI,IAAI,SAAS,CAAC,EAAE,YAAY,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,CAAC;IAE9E,MAAM,SAAS,GAAG,MAAM,MAAM,CAAC,IAAI;SAChC,KAAK,CAAC,mCAAmC,EAAE,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC;SACtD,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,sBAAsB,EAAE,CAAC,CAAC;SAC1D,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE,CAAC,CAAC;QAC1B,EAAE,EAAE,KAAK;QACT,MAAM,EAAE,sEAAsE;cAC1E,+CAA+C,SAAS,CAAC,KAAK,CAAC,EAAE;KACtE,CAAC,CAAC,CAAC;IAEN,MAAM,IAAI,GAAG,MAAM,IAAI,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC;SACxC,SAAS,EAAE;SACX,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,oBAAoB,KAAK,CAAC,MAAM,SAAS,EAAE,CAAC,CAAC;SAClF,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE,CAAC,CAAC;QAC1B,EAAE,EAAE,KAAK;QACT,MAAM,EAAE,6EAA6E;cACjF,wCAAwC,SAAS,CAAC,KAAK,CAAC,EAAE;KAC/D,CAAC,CAAC,CAAC;IAEN,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC;AAC7B,CAAC;AAED,kBAAkB;AAElB,MAAM,UAAU,GACd,6BAA6B;MAC3B,mEAAmE,CAAC;AACxE,MAAM,cAAc,GAClB,4EAA4E,CAAC;AAC/E,MAAM,YAAY,GAChB,yBAAyB;MACvB,yDAAyD;MACzD,uFAAuF;MACvF,oFAAoF,CAAC;AAEzF,KAAK,UAAU,GAAG,CAAC,QAAgB;IACjC,MAAM,EAAE,GAAG,eAAe,CAAC,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IAC7E,IAAI,CAAC;QACH,OAAO,CAAC,MAAM,IAAI,OAAO,CAAS,CAAC,OAAO,EAAE,EAAE,CAAC,EAAE,CAAC,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IACzF,CAAC;YAAS,CAAC;QACT,EAAE,CAAC,KAAK,EAAE,CAAC;IACb,CAAC;AACH,CAAC;AAED;;;;;;;GAOG;AACH,KAAK,UAAU,SAAS,CAAC,QAAgB;IACvC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;IAC/B,MAAM,IAAI,GAAG,IAAI,QAAQ,CAAC;QACxB,KAAK,CAAC,MAAM,EAAE,SAAS,EAAE,IAAI;YAC3B,IAAI,EAAE,CAAC;QACT,CAAC;KACF,CAAC,CAAC;IACH,MAAM,EAAE,GAAG,eAAe,CAAC,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;IACnF,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,IAAI,OAAO,CAAS,CAAC,OAAO,EAAE,EAAE,CAAC,EAAE,CAAC,QAAQ,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC,CAAC;QAChF,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAC3B,OAAO,MAAM,CAAC,IAAI,EAAE,CAAC;IACvB,CAAC;YAAS,CAAC;QACT,EAAE,CAAC,KAAK,EAAE,CAAC;IACb,CAAC;AACH,CAAC;AAED,wBAAwB;AAExB,SAAS,GAAG,CAAC,OAAe,EAAE,IAAc;IAC1C,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,yEAAyE;QACzE,4EAA4E;QAC5E,iBAAiB;QACjB,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAC;QACxD,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;QAC1B,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,EAAE;YACzB,IAAI,IAAI,KAAK,CAAC;gBAAE,OAAO,EAAE,CAAC;;gBACrB,MAAM,CAAC,IAAI,KAAK,CAAC,GAAG,OAAO,WAAW,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;QAC9D,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC;AAED,SAAS,cAAc,CAAC,MAAsB;IAC5C,OAAO,MAAM,CAAC,IAAI,KAAK,UAAU;QAC/B,CAAC,CAAC,4BAA4B,gBAAgB,IAAI;QAClD,CAAC,CAAC,GAAG,MAAM,CAAC,IAAI,aAAa,CAAC;AAClC,CAAC;AAED,SAAS,UAAU,CAAC,KAAa;IAC/B,OAAO,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,CAAC;AAC7C,CAAC;AAED,SAAS,SAAS,CAAC,KAAc;IAC/B,OAAO,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAChE,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@floh-solutions/pharos-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Azure DevOps from a headless shell, for an agent: the whole context of a task in one call, and the wiki/comment verbs Microsoft's MCP server does not ship.",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"author": "FLOH Solutions",
|
|
@@ -17,14 +17,15 @@
|
|
|
17
17
|
"pharos": "./dist/bin.js"
|
|
18
18
|
},
|
|
19
19
|
"files": [
|
|
20
|
-
"dist"
|
|
20
|
+
"dist",
|
|
21
|
+
"skill"
|
|
21
22
|
],
|
|
22
23
|
"engines": {
|
|
23
24
|
"node": ">=22"
|
|
24
25
|
},
|
|
25
26
|
"dependencies": {
|
|
26
|
-
"@floh-solutions/
|
|
27
|
-
"@floh-solutions/
|
|
27
|
+
"@floh-solutions/ado-core": "0.1.0",
|
|
28
|
+
"@floh-solutions/plan-to-board": "0.1.1"
|
|
28
29
|
},
|
|
29
30
|
"devDependencies": {
|
|
30
31
|
"@types/node": "^22.10.2",
|
package/skill/SKILL.md
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: pharos
|
|
3
|
+
description: "Use whenever Azure DevOps work is involved — reading or updating a work item, task, bug, story or epic; anything with a board, backlog, sprint or iteration; reading or writing a project wiki page; commenting on a task or a wiki page; linking a plan to an epic; or when the user names a work item by number (\"pick up 4821\", \"what's on 210\"). Also use when deciding between the Azure DevOps MCP server and the `pharos` CLI, or when an Azure DevOps call fails and you need to know whether to retry it."
|
|
4
|
+
license: Proprietary
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Working Azure DevOps with `pharos`
|
|
8
|
+
|
|
9
|
+
`pharos` is a CLI that gives you the full Azure DevOps surface from a shell,
|
|
10
|
+
authenticated by an environment variable rather than a browser login. Run
|
|
11
|
+
`pharos --help` for the complete verb list; this skill is the part that is not
|
|
12
|
+
in the help text.
|
|
13
|
+
|
|
14
|
+
## Start every task with one command
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
pharos task <id>
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
That returns the work item, its comments, its attachments, its relations **with
|
|
21
|
+
their titles**, and the full content of every linked wiki page **plus the
|
|
22
|
+
discussion on those pages** — in one call.
|
|
23
|
+
|
|
24
|
+
**Do this before anything else, and do not assemble it yourself.** By hand it is
|
|
25
|
+
five or six lookups across two tools, and the plan a colleague wrote is usually
|
|
26
|
+
on a linked wiki page rather than in the description. A task worked without it
|
|
27
|
+
is a task worked without the plan.
|
|
28
|
+
|
|
29
|
+
Add `--pretty` when a person will read the output.
|
|
30
|
+
|
|
31
|
+
Anything that could not be fetched appears in `problems[]`. **If that array is
|
|
32
|
+
not empty, say so before acting** — a context with an invisible hole in it gets
|
|
33
|
+
reasoned from confidently.
|
|
34
|
+
|
|
35
|
+
## Which tool: `pharos` or the Azure DevOps MCP server
|
|
36
|
+
|
|
37
|
+
If both are available, they overlap. The dividing line:
|
|
38
|
+
|
|
39
|
+
| | |
|
|
40
|
+
|---|---|
|
|
41
|
+
| **Use `pharos`** | anything touching a **wiki page's comments** (the MCP has no tool for these at all), deleting a comment, reactions, deleting a wiki page, and `pharos task <id>` for gathering context |
|
|
42
|
+
| **Either works** | reading and querying work items, creating them, updating fields and state, adding a comment, reading a wiki page |
|
|
43
|
+
| **The MCP may be better** | code search, repository and pull request work, anything outside work items and wikis |
|
|
44
|
+
|
|
45
|
+
When in doubt use `pharos`: its failure modes are structured and its guards are
|
|
46
|
+
explicit.
|
|
47
|
+
|
|
48
|
+
## Reading the outcome
|
|
49
|
+
|
|
50
|
+
Output is a contract. **Success is JSON on stdout; failure is JSON on stderr
|
|
51
|
+
with a non-zero exit.** An empty array with exit 0 is a query that matched
|
|
52
|
+
nothing — a different fact from a failure.
|
|
53
|
+
|
|
54
|
+
| exit | meaning | what to do |
|
|
55
|
+
|---|---|---|
|
|
56
|
+
| `0` | it worked | carry on |
|
|
57
|
+
| `1` | the call failed | check `kind`; retry only if it is `rateLimit` (wait `retryAfterMs`) or transient |
|
|
58
|
+
| `2` | called wrong, or not configured | **never retry unchanged.** Fix the call, or the setup |
|
|
59
|
+
| `3` | a guard here refused | re-run with `--yes`, or raise `--max-writes` — after deciding it is right |
|
|
60
|
+
|
|
61
|
+
`"kind": "conflict"` means somebody wrote first. Your work is still valid:
|
|
62
|
+
re-read, re-apply. It carries both revisions — and note that **posting a comment
|
|
63
|
+
bumps `System.Rev`**, so a revision mismatch is not proof anybody edited the
|
|
64
|
+
same field.
|
|
65
|
+
|
|
66
|
+
## Destructive verbs refuse by default. Read the refusal.
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
pharos wiki delete /Plans/Old # exit 3, nothing changed
|
|
70
|
+
pharos wiki delete /Plans/Old --yes # done
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
**Exit 3 with `"kind": "refused"` means nothing happened.** Do not report the
|
|
74
|
+
work as done. The refusal carries a preview of what it would have done — check
|
|
75
|
+
that preview is what you intended before adding `--yes`, rather than reflexively
|
|
76
|
+
re-running with the flag.
|
|
77
|
+
|
|
78
|
+
Replacing a wiki page needs `--yes`; creating one does not. The refusal tells
|
|
79
|
+
you how many bytes are at stake, which is how you notice you are about to
|
|
80
|
+
overwrite somebody's page instead of writing a new one.
|
|
81
|
+
|
|
82
|
+
`--dry-run` gives the preview with exit 0 when you want to look without being
|
|
83
|
+
refused.
|
|
84
|
+
|
|
85
|
+
## The traps are handled. Do not work around them.
|
|
86
|
+
|
|
87
|
+
Every known Azure DevOps failure of this kind is handled inside the tool: the
|
|
88
|
+
lost-update on wiki writes, the deleted-comment field that lies, the reaction
|
|
89
|
+
call that needs an empty body, artifact links that must carry a project GUID,
|
|
90
|
+
relation removal that would otherwise take the wrong link.
|
|
91
|
+
|
|
92
|
+
**So if something looks like it needs a workaround, it does not.** A `pharos`
|
|
93
|
+
command failing means the request was genuinely wrong or the API genuinely
|
|
94
|
+
refused — read the error rather than reaching for `curl`. If you find a real gap,
|
|
95
|
+
say so plainly; do not paper over it with raw REST calls that skip the guards.
|
|
96
|
+
|
|
97
|
+
## Three things no tool can fix
|
|
98
|
+
|
|
99
|
+
1. **A wiki is a git repository.** Two writes to the same wiki at the same
|
|
100
|
+
moment are two pushes racing for one HEAD. Write pages one at a time.
|
|
101
|
+
2. **There is no wiki event in service hooks.** You cannot subscribe to "a wiki
|
|
102
|
+
page changed" — only to `git.push` on the wiki's repository. If you need to
|
|
103
|
+
react to wiki edits, that is the only route.
|
|
104
|
+
3. **Wiki ancestors are not created for you.** Writing `/A/B` when `/A` does not
|
|
105
|
+
exist is refused. Build a page tree top-down, one write per level.
|
|
106
|
+
|
|
107
|
+
## The loop
|
|
108
|
+
|
|
109
|
+
Working a task end to end, in the order that keeps the board honest:
|
|
110
|
+
|
|
111
|
+
1. `pharos task <id>` — read everything, including the linked plan.
|
|
112
|
+
2. If a plan is needed, write it: `pharos wiki write /Plans/<name> --stdin`,
|
|
113
|
+
then link it to the epic so the next person finds it the same way you did.
|
|
114
|
+
3. Break it down — create the child items.
|
|
115
|
+
4. `pharos comment add <id> --file notes.md` — decisions belong on the item,
|
|
116
|
+
not only in a chat log that nobody else can read.
|
|
117
|
+
5. Move the state when the work moves, not at the end.
|
|
118
|
+
|
|
119
|
+
**Every person uses their own token.** Board attribution is per-person, so
|
|
120
|
+
anything you do is recorded against whoever owns `ADO_PAT`. Never suggest
|
|
121
|
+
sharing one.
|
|
122
|
+
|
|
123
|
+
## When it is not set up
|
|
124
|
+
|
|
125
|
+
`"kind": "config"` on exit 2 means the environment is missing. Run:
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
pharos setup
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
It asks for the organisation, project and token, stores the token in the OS
|
|
132
|
+
keychain rather than a file, writes the right shell profile, and verifies both
|
|
133
|
+
permission scopes — Work Items and Wiki are separate in Azure DevOps, and a
|
|
134
|
+
token missing the second one works until the first wiki write.
|
|
135
|
+
|
|
136
|
+
Variables live in the environment (`ADO_ORG`, `ADO_PROJECT`, `ADO_PAT`) and
|
|
137
|
+
**never in the tool's own config**, which is what lets the same command run in
|
|
138
|
+
CI and unattended. After setup, a **new** shell is needed.
|