roger-roger 0.1.2 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/package.json +1 -1
- package/skills/roger-roger/SKILL.md +1 -1
- package/skills/roger-roger/herdr-plugin.toml +1 -1
- package/skills/roger-roger/scripts/help.mjs +3 -3
- package/skills/roger-roger/scripts/install.mjs +153 -9
- package/skills/roger-roger/scripts/roger-roger.mjs +5 -1
package/README.md
CHANGED
|
@@ -57,6 +57,7 @@ A missing key is not fatal. Speech falls back to the computer's built-in voice (
|
|
|
57
57
|
`roger-roger install` asks, in the terminal:
|
|
58
58
|
|
|
59
59
|
- which agents should have the skill (every one it finds is ticked)
|
|
60
|
+
- whether to tell your agents to always use it: a marked block in each agent's global instructions (`~/.claude/CLAUDE.md`, `~/.codex/AGENTS.md`, `~/.config/opencode/AGENTS.md`, `~/.gemini/GEMINI.md`), which uninstall takes out again
|
|
60
61
|
- whether to register with Herdr, if it's installed
|
|
61
62
|
- how to reach you: Slack, sound, speech, in any combination
|
|
62
63
|
- Slack: it connects it for you (see above), and where messages go (your DMs, or a channel)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "roger-roger",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "Bring yourself back to the conversation: your coding agents reach you by Slack, a sound or a spoken line when they finish, get stuck, or need a decision, and you answer from your phone.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -261,7 +261,7 @@ Judge it by what happens if you are wrong, not by whether it feels routine. A fo
|
|
|
261
261
|
## Other commands
|
|
262
262
|
|
|
263
263
|
- `install`: the interactive setup, for the user to run in a terminal (section 1). `install --yes` asks nothing: it links the skill for every agent on the machine and registers with Herdr, and prints what it did.
|
|
264
|
-
- `uninstall`: takes it out again, for the user to run in a terminal (`npx roger-roger@latest uninstall`): hooks, skill links, the Herdr plugin, the daemon and the tray, settings and tokens, the global command. `--yes` removes all of that without asking (`--slack-cli` adds the Slack CLI). Never run it for the user unasked.
|
|
264
|
+
- `uninstall`: takes it out again, for the user to run in a terminal (`npx roger-roger@latest uninstall`): the standing instruction `install` put in the agents' global files (a marked block in `CLAUDE.md`, `AGENTS.md`, `GEMINI.md`), hooks, skill links, the Herdr plugin, the daemon and the tray, settings and tokens, the global command. `--yes` removes all of that without asking (`--slack-cli` adds the Slack CLI). Never run it for the user unasked.
|
|
265
265
|
- `help [command]`: the commands, and one command's flags.
|
|
266
266
|
- `status`: current config, whether the tokens are present, the daemon, running sessions, and every valid option value.
|
|
267
267
|
- `slack-setup [status|install-cli|login|create]`: the Slack steps `install` runs, one at a time, for repairing a connection by hand. `create` again is safe: it updates the same app, which is what to do after the manifest changed or when a scope is missing.
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
id = "roger-roger"
|
|
8
8
|
name = "roger-roger"
|
|
9
|
-
version = "0.1.
|
|
9
|
+
version = "0.1.4"
|
|
10
10
|
min_herdr_version = "0.9.0"
|
|
11
11
|
description = "Slack, a sound or a spoken line from the agents in your panes, and questions you answer from your phone"
|
|
12
12
|
platforms = ["linux", "macos", "windows"]
|
|
@@ -10,8 +10,8 @@ export const TAGLINE = "Bring yourself back to the conversation: your agents rea
|
|
|
10
10
|
/** Every command: where it belongs, how it is called, what it does, and (for the busy ones) its flags. */
|
|
11
11
|
export const COMMANDS = [
|
|
12
12
|
// ---- setting up
|
|
13
|
-
{ section: "Setting up", name: "install", usage: "[--yes]", summary: "Set everything up in the terminal: the command on your PATH, the skill for every agent, Herdr, Slack, the sound and the voice, hooks, tray. Run it again to change anything. --yes asks nothing and only links the skill.", flags: [["--yes", "no questions: link the skill for every agent on the machine and register with Herdr, then print what was done"]] },
|
|
14
|
-
{ section: "Setting up", name: "uninstall", usage: "[--yes]", summary: "Take it out again: hooks, links, Herdr, the daemon and the tray, settings and tokens, the global command.", flags: [["--yes", "no questions: remove everything except the Slack CLI, then print what was done"]] },
|
|
13
|
+
{ section: "Setting up", name: "install", usage: "[--yes]", summary: "Set everything up in the terminal: the command on your PATH, the skill for every agent and a standing instruction to use it, Herdr, Slack, the sound and the voice, hooks, tray. Run it again to change anything. --yes asks nothing and only links the skill.", flags: [["--yes", "no questions: link the skill for every agent on the machine and register with Herdr, then print what was done. Nothing is written to CLAUDE.md or AGENTS.md"]] },
|
|
14
|
+
{ section: "Setting up", name: "uninstall", usage: "[--yes]", summary: "Take it out again: the standing instruction, hooks, links, Herdr, the daemon and the tray, settings and tokens, the global command.", flags: [["--yes", "no questions: remove everything except the Slack CLI, then print what was done"], ["--slack-cli", "with --yes, remove the Slack CLI and its login as well"]] },
|
|
15
15
|
{ section: "Setting up", name: "status", usage: "", summary: "Settings, whether the tokens are there, the daemon, who is running, and every valid option value." },
|
|
16
16
|
{
|
|
17
17
|
section: "Setting up", name: "setup", usage: "[flags]", summary: "Change any setting; the rest are kept.",
|
|
@@ -180,7 +180,7 @@ export function helpText(command = "", { columns = process.stdout.columns || 100
|
|
|
180
180
|
for (const line of wrap(TAGLINE, cols - 2)) out.push(` ${c.dim(line)}`);
|
|
181
181
|
out.push("");
|
|
182
182
|
out.push(` ${c.bold("Usage")}`);
|
|
183
|
-
out.push(...table([["roger-roger <command> [flags]", "every command prints one JSON object"], ["roger-roger help <command>", "that command's flags"]], {
|
|
183
|
+
out.push(...table([["roger-roger <command> [flags]", "every command prints one JSON object"], ["roger-roger help <command>", "that command's flags"], ["roger-roger --version", "which copy this is"]], {
|
|
184
184
|
indent: 4, columns: cols, c, maxLeft: 30, left: (l) => c.cyan("roger-roger") + " " + c.dim(l.slice("roger-roger ".length)),
|
|
185
185
|
}));
|
|
186
186
|
const sections = [...new Set(COMMANDS.map((x) => x.section))];
|
|
@@ -95,6 +95,81 @@ export function applyLink(link, target, plan, fsx = fs, now = new Date()) {
|
|
|
95
95
|
return { ...plan, done: true };
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
+
// ---------------------------------------------------------------- the agents' standing instructions
|
|
99
|
+
|
|
100
|
+
/** Each agent's global instructions file: what it reads at the start of every session, everywhere. */
|
|
101
|
+
export const INSTRUCTION_FILES = [
|
|
102
|
+
{ id: "claude", label: "Claude Code", dir: [".claude"], file: "CLAUDE.md" },
|
|
103
|
+
{ id: "codex", label: "Codex", dir: [".codex"], file: "AGENTS.md" },
|
|
104
|
+
{ id: "opencode", label: "OpenCode", dir: [".config", "opencode"], file: "AGENTS.md" },
|
|
105
|
+
{ id: "gemini", label: "Gemini CLI", dir: [".gemini"], file: "GEMINI.md" },
|
|
106
|
+
];
|
|
107
|
+
|
|
108
|
+
const MARK_START = "<!-- roger-roger:start -->";
|
|
109
|
+
const MARK_END = "<!-- roger-roger:end -->";
|
|
110
|
+
|
|
111
|
+
/** The standing instruction, between marks so it can be replaced or removed without touching anything else. */
|
|
112
|
+
export const INSTRUCTIONS = [
|
|
113
|
+
MARK_START,
|
|
114
|
+
"- Always use the `roger-roger` skill (the `roger-roger` command, or `node <skill-dir>/scripts/roger-roger.mjs`): at the end of every turn, whatever its size, send a notification as the last action before the final reply, and use `ask` when blocked on a decision. Don't skip it because the task seemed quick or the user seems to be at the keyboard. Pass `--session` (three to six words about the work) and `--agent-model` on every call. If the skill isn't available, say so once.",
|
|
115
|
+
MARK_END,
|
|
116
|
+
].join("\n");
|
|
117
|
+
|
|
118
|
+
/** Where each agent's file is, whether that agent is here, and whether the block is already in it. */
|
|
119
|
+
export function instructionFiles(home = os.homedir(), fsx = fs) {
|
|
120
|
+
return INSTRUCTION_FILES.map((a) => {
|
|
121
|
+
const root = path.join(home, ...a.dir);
|
|
122
|
+
const file = path.join(root, a.file);
|
|
123
|
+
let text = "";
|
|
124
|
+
try {
|
|
125
|
+
text = fsx.readFileSync(file, "utf8");
|
|
126
|
+
} catch {}
|
|
127
|
+
return { ...a, root, path: file, present: fsx.existsSync(root), has: text.includes(MARK_START) };
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const BLOCK = new RegExp(`\\n?${MARK_START}[\\s\\S]*?${MARK_END}\\n?`);
|
|
132
|
+
|
|
133
|
+
/** The text with the block in it, replacing an older one or added at the end. */
|
|
134
|
+
export function withInstructions(text) {
|
|
135
|
+
const body = String(text ?? "");
|
|
136
|
+
if (body.includes(MARK_START) && body.includes(MARK_END)) return body.replace(BLOCK, `\n${INSTRUCTIONS}\n`).replace(/^\n/, "");
|
|
137
|
+
if (!body.trim()) return `${INSTRUCTIONS}\n`;
|
|
138
|
+
return `${body.replace(/\s*$/, "")}\n\n${INSTRUCTIONS}\n`;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** The text without the block; anything else in it is left exactly as it was. */
|
|
142
|
+
export function withoutInstructions(text) {
|
|
143
|
+
const body = String(text ?? "");
|
|
144
|
+
if (!body.includes(MARK_START)) return body;
|
|
145
|
+
return body.replace(BLOCK, "\n").replace(/\n{3,}/g, "\n\n").replace(/^\n+/, "").replace(/\n+$/, "\n");
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export function addInstructions(file, fsx = fs) {
|
|
149
|
+
let text = "";
|
|
150
|
+
try {
|
|
151
|
+
text = fsx.readFileSync(file, "utf8");
|
|
152
|
+
} catch {}
|
|
153
|
+
const had = text.includes(MARK_START);
|
|
154
|
+
fsx.mkdirSync(path.dirname(file), { recursive: true });
|
|
155
|
+
fsx.writeFileSync(file, withInstructions(text), "utf8");
|
|
156
|
+
return { file, updated: had, added: !had };
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export function removeInstructions(file, fsx = fs) {
|
|
160
|
+
let text;
|
|
161
|
+
try {
|
|
162
|
+
text = fsx.readFileSync(file, "utf8");
|
|
163
|
+
} catch {
|
|
164
|
+
return { file, removed: false };
|
|
165
|
+
}
|
|
166
|
+
if (!text.includes(MARK_START)) return { file, removed: false };
|
|
167
|
+
const rest = withoutInstructions(text);
|
|
168
|
+
if (rest.trim()) fsx.writeFileSync(file, rest, "utf8");
|
|
169
|
+
else fsx.rmSync(file, { force: true }); // ours was all there was
|
|
170
|
+
return { file, removed: true };
|
|
171
|
+
}
|
|
172
|
+
|
|
98
173
|
// ---------------------------------------------------------------- the global copy
|
|
99
174
|
|
|
100
175
|
/**
|
|
@@ -115,23 +190,37 @@ export function globalCopy(env = process.env) {
|
|
|
115
190
|
const root = (r.stdout ?? "").trim();
|
|
116
191
|
if (r.status !== 0 || !root) return null;
|
|
117
192
|
const pkg = path.join(root, "roger-roger");
|
|
118
|
-
|
|
193
|
+
let version = "";
|
|
194
|
+
try {
|
|
195
|
+
version = JSON.parse(fs.readFileSync(path.join(pkg, "package.json"), "utf8")).version ?? "";
|
|
196
|
+
} catch {}
|
|
197
|
+
return { root, dir: path.join(pkg, "skills", "roger-roger"), present: Boolean(version), version };
|
|
119
198
|
} catch {
|
|
120
199
|
return null;
|
|
121
200
|
}
|
|
122
201
|
}
|
|
123
202
|
|
|
203
|
+
/** a < b for dotted versions; anything unparseable counts as older. */
|
|
204
|
+
export function olderThan(a, b) {
|
|
205
|
+
const parse = (v) => String(v ?? "").split(".").map((n) => parseInt(n, 10) || 0);
|
|
206
|
+
const [x, y] = [parse(a), parse(b)];
|
|
207
|
+
for (let i = 0; i < 3; i++) if ((x[i] ?? 0) !== (y[i] ?? 0)) return (x[i] ?? 0) < (y[i] ?? 0);
|
|
208
|
+
return false;
|
|
209
|
+
}
|
|
210
|
+
|
|
124
211
|
/**
|
|
125
212
|
* Which copy of the skill everything should point at, and whether to offer a global install:
|
|
126
213
|
* the copy this runs from, unless that is a passing one (npx's cache, say) and there is no global
|
|
127
|
-
* copy yet
|
|
214
|
+
* copy yet, or the global copy is older than this one (`npx roger-roger@latest install` after an
|
|
215
|
+
* update). A global copy that is as new as a checkout or a skills.sh copy is left alone: whoever
|
|
128
216
|
* runs this copy meant this copy.
|
|
129
217
|
*/
|
|
130
|
-
export function pickTarget(running, global) {
|
|
218
|
+
export function pickTarget(running, global, runningVersion = VERSION) {
|
|
131
219
|
if (!global) return { target: running, offer: false, reason: "npm was not found" };
|
|
132
220
|
if (norm(running) === norm(global.dir)) return { target: running, offer: false, reason: "global" };
|
|
133
|
-
if (global.present) return { target: running, offer:
|
|
134
|
-
return { target: running, offer: true, reason: "
|
|
221
|
+
if (!global.present) return { target: running, offer: true, reason: "no global copy yet" };
|
|
222
|
+
if (runningVersion && olderThan(global.version, runningVersion)) return { target: running, offer: true, reason: "update", from: global.version, to: runningVersion };
|
|
223
|
+
return { target: running, offer: false, reason: "a global copy exists, but this one was run" };
|
|
135
224
|
}
|
|
136
225
|
|
|
137
226
|
/** `npm install -g roger-roger@latest`. Throws with npm's last words when it fails. */
|
|
@@ -422,9 +511,11 @@ export async function runInstall(args, { script, hooks, input = process.stdin, o
|
|
|
422
511
|
if (pick.reason === "global") {
|
|
423
512
|
ui.done("The roger-roger command is on your PATH", global.dir);
|
|
424
513
|
} else if (pick.offer) {
|
|
425
|
-
const yes = await ui.confirm(
|
|
514
|
+
const yes = await ui.confirm(pick.reason === "update"
|
|
515
|
+
? { message: `Update the roger-roger command from ${pick.from} to ${pick.to}?`, hint: "npm install -g roger-roger@latest; the old one would keep answering" }
|
|
516
|
+
: { message: "Put the roger-roger command on your PATH?", hint: "npm install -g roger-roger, so you and your agents can run it from anywhere" });
|
|
426
517
|
if (yes) {
|
|
427
|
-
const done = await attempt(ui, "Installing globally", () => ui.run("Installing roger-roger with npm
|
|
518
|
+
const done = await attempt(ui, pick.reason === "update" ? "Updating" : "Installing globally", () => ui.run(`${pick.reason === "update" ? "Updating" : "Installing"} roger-roger with npm`, () => installGlobally(env), { done: () => ({ text: `The roger-roger command is on your PATH${pick.to ? `, at ${pick.to}` : ""}`, detail: global.dir }) }), { skipLabel: "Carry on with this copy" });
|
|
428
519
|
if (done !== null && fs.existsSync(path.join(global.dir, "scripts", "roger-roger.mjs"))) {
|
|
429
520
|
target = global.dir;
|
|
430
521
|
hookScript = path.join(global.dir, "scripts", "roger-roger.mjs");
|
|
@@ -462,6 +553,38 @@ export async function runInstall(args, { script, hooks, input = process.stdin, o
|
|
|
462
553
|
}
|
|
463
554
|
}
|
|
464
555
|
|
|
556
|
+
// ---- the standing instruction, so agents use it without being told each time
|
|
557
|
+
const instructionTargets = instructionFiles(home).filter((f) => f.present);
|
|
558
|
+
if (instructionTargets.length) {
|
|
559
|
+
const already = instructionTargets.filter((f) => f.has);
|
|
560
|
+
const yes = await ui.confirm({
|
|
561
|
+
message: already.length === instructionTargets.length ? "Keep telling your agents to always use roger-roger?" : "Tell your agents to always use roger-roger?",
|
|
562
|
+
yes: "Yes (recommended)",
|
|
563
|
+
hint: `a marked block in ${instructionTargets.map((f) => `~/${[...f.dir, f.file].join("/")}`).join(", ")}`,
|
|
564
|
+
initial: true,
|
|
565
|
+
});
|
|
566
|
+
if (yes) {
|
|
567
|
+
for (const f of instructionTargets) {
|
|
568
|
+
try {
|
|
569
|
+
const r = addInstructions(f.path);
|
|
570
|
+
ui.done(`${f.label}: ${r.added ? "instructions added" : "instructions refreshed"}`, f.path);
|
|
571
|
+
summary.push(`instructions: ${f.label}`);
|
|
572
|
+
} catch (e) {
|
|
573
|
+
ui.failed(`${f.label}: could not write ${f.path}`, e.message);
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
} else if (already.length) {
|
|
577
|
+
for (const f of already) {
|
|
578
|
+
try {
|
|
579
|
+
removeInstructions(f.path);
|
|
580
|
+
ui.done(`${f.label}: instructions removed`, f.path);
|
|
581
|
+
} catch (e) {
|
|
582
|
+
ui.failed(`${f.label}: could not change ${f.path}`, e.message);
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
|
|
465
588
|
// ---- herdr
|
|
466
589
|
let herdrDone = false;
|
|
467
590
|
if (herdr) {
|
|
@@ -732,6 +855,7 @@ export async function uninstallFindings({ home = os.homedir(), env = process.env
|
|
|
732
855
|
const global = globalCopy(env);
|
|
733
856
|
return {
|
|
734
857
|
links: skillFootprint(home),
|
|
858
|
+
instructions: instructionFiles(home).filter((f) => f.has),
|
|
735
859
|
herdr: herdr && herdrHasPlugin(herdr) ? herdr : null,
|
|
736
860
|
hookAgents,
|
|
737
861
|
home: [rogerRogerHome(env), path.join(home, ".attention")].filter((d) => fs.existsSync(d)),
|
|
@@ -743,7 +867,15 @@ export async function uninstallFindings({ home = os.homedir(), env = process.env
|
|
|
743
867
|
/** Everything but the Slack CLI, with no questions. Returns what was done. */
|
|
744
868
|
export async function quietUninstall({ home = os.homedir(), env = process.env, hooks = null, script = "", slackCli = false } = {}) {
|
|
745
869
|
const found = await uninstallFindings({ home, env, hooks, script });
|
|
746
|
-
const done = { daemonStopped: await stopDaemon(env), hooks: [], links: [], herdr: null, home: [], slackCli: false, global: null };
|
|
870
|
+
const done = { daemonStopped: await stopDaemon(env), hooks: [], links: [], instructions: [], herdr: null, home: [], slackCli: false, global: null };
|
|
871
|
+
for (const f of found.instructions) {
|
|
872
|
+
try {
|
|
873
|
+
removeInstructions(f.path);
|
|
874
|
+
done.instructions.push(f.path);
|
|
875
|
+
} catch (e) {
|
|
876
|
+
done.instructions.push({ file: f.path, error: e.message });
|
|
877
|
+
}
|
|
878
|
+
}
|
|
747
879
|
for (const name of found.hookAgents) {
|
|
748
880
|
try {
|
|
749
881
|
hooks.agents[name].uninstall();
|
|
@@ -797,6 +929,7 @@ export async function runUninstall(args, { script, hooks, input = process.stdin,
|
|
|
797
929
|
const labels = { claude: "Claude Code", opencode: "OpenCode", codex: "Codex" };
|
|
798
930
|
const choices = [];
|
|
799
931
|
if (found.links.length) choices.push({ value: "links", label: "The skill, for agents", hint: found.links.map((a) => a.label).join(", "), checked: true });
|
|
932
|
+
if (found.instructions.length) choices.push({ value: "instructions", label: "The standing instruction", hint: found.instructions.map((f) => `~/${[...f.dir, f.file].join("/")}`).join(", "), checked: true });
|
|
800
933
|
if (found.hookAgents.length) choices.push({ value: "hooks", label: "Terminal-question hooks", hint: found.hookAgents.map((n) => labels[n] ?? n).join(", "), checked: true });
|
|
801
934
|
if (found.herdr) choices.push({ value: "herdr", label: "The Herdr plugin", hint: "herdr plugin unlink", checked: true });
|
|
802
935
|
if (found.home.length) choices.push({ value: "home", label: "Settings, Slack tokens, state", hint: found.home.join(", "), checked: true });
|
|
@@ -830,6 +963,17 @@ export async function runUninstall(args, { script, hooks, input = process.stdin,
|
|
|
830
963
|
}
|
|
831
964
|
}
|
|
832
965
|
}
|
|
966
|
+
if (what.includes("instructions")) {
|
|
967
|
+
for (const f of found.instructions) {
|
|
968
|
+
try {
|
|
969
|
+
removeInstructions(f.path);
|
|
970
|
+
ui.done(`${f.label}: instruction removed`, f.path);
|
|
971
|
+
removed.push(`instructions: ${f.label}`);
|
|
972
|
+
} catch (e) {
|
|
973
|
+
ui.failed(`${f.label}: could not change ${f.path}`, e.message);
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
}
|
|
833
977
|
if (what.includes("herdr")) {
|
|
834
978
|
const r = herdrUnlink(found.herdr);
|
|
835
979
|
if (r.ok) {
|
|
@@ -873,7 +1017,7 @@ export async function runUninstall(args, { script, hooks, input = process.stdin,
|
|
|
873
1017
|
"",
|
|
874
1018
|
...removed.map((s) => `${c.green("•")} ${s}`),
|
|
875
1019
|
"",
|
|
876
|
-
c.dim("
|
|
1020
|
+
c.dim("Only the marked roger-roger block was taken out of your CLAUDE.md or AGENTS.md; anything you wrote yourself is still there."),
|
|
877
1021
|
`Back any time: ${c.cyan("npx roger-roger@latest install")}`,
|
|
878
1022
|
]);
|
|
879
1023
|
return 0;
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
import fs from "node:fs";
|
|
11
11
|
import path from "node:path";
|
|
12
12
|
import {
|
|
13
|
-
DEFAULTS, DUCK_MODES, KINDS, METHODS, ON_EXPIRE, SPEECH_MODES, WHEN_MODES,
|
|
13
|
+
DEFAULTS, DUCK_MODES, KINDS, METHODS, ON_EXPIRE, SPEECH_MODES, VERSION, WHEN_MODES,
|
|
14
14
|
applySetup, rogerRogerHome, configPath, listArg, listSounds, loadConfig, migrateLegacyHome, parseArgs,
|
|
15
15
|
readSlackCredentials, saveConfig, slackAppToken, slackToken,
|
|
16
16
|
} from "./lib.mjs";
|
|
@@ -324,6 +324,10 @@ const AGENTS = {
|
|
|
324
324
|
|
|
325
325
|
/** `help`, `help <command>`, `--help`, or a command that doesn't exist: the overview, or one command's page. */
|
|
326
326
|
function help(command, args) {
|
|
327
|
+
if (["--version", "-v", "-V", "version"].includes(command)) {
|
|
328
|
+
process.stdout.write(`${VERSION || "unversioned copy"}\n`);
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
327
331
|
const wanted = command === "help" ? args._[0] : "";
|
|
328
332
|
const unknown = command && !["help", "--help", "-h", undefined].includes(command);
|
|
329
333
|
if (unknown) process.stderr.write(`roger-roger: unknown command "${command}"\n`);
|