@coderook/cli 0.14.1 → 0.16.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/.claude-plugin/plugin.json +10 -0
- package/README.md +67 -15
- package/dist/cli/src/cli.js +33 -0
- package/dist/cli/src/skill_command.js +113 -0
- package/package.json +7 -4
- package/skills/coderook/SKILL.md +130 -0
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "coderook",
|
|
3
|
+
"displayName": "CodeRook",
|
|
4
|
+
"description": "Save, browse and restore whole-snapshot versions of a project on CodeRook, from Claude Code.",
|
|
5
|
+
"version": "0.16.0",
|
|
6
|
+
"author": { "name": "ACCA Gaming Productions", "url": "https://coderook.com" },
|
|
7
|
+
"homepage": "https://coderook.com/docs#assistants",
|
|
8
|
+
"license": "SEE LICENSE IN LICENSE.txt",
|
|
9
|
+
"keywords": ["coderook", "versioning", "snapshots", "backup"]
|
|
10
|
+
}
|
package/README.md
CHANGED
|
@@ -122,36 +122,88 @@ it helps, raw where it does not, and a SHA-256 for every chunk and file.
|
|
|
122
122
|
Extraction is verified and atomic — a damaged bundle fails rather than leaving
|
|
123
123
|
a half-written tree.
|
|
124
124
|
|
|
125
|
-
## Claude Code
|
|
125
|
+
## Claude Code
|
|
126
126
|
|
|
127
|
-
|
|
128
|
-
|
|
127
|
+
```
|
|
128
|
+
coderook skill
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
That is the whole setup. It writes a short guide into Claude Code's skills
|
|
132
|
+
folder, and from then on you ask for what you want in ordinary words:
|
|
133
|
+
|
|
134
|
+
> save this to CodeRook
|
|
135
|
+
|
|
136
|
+
> what have I changed?
|
|
137
|
+
|
|
138
|
+
> what versions does this project have?
|
|
139
|
+
|
|
140
|
+
Claude works out which commands to run. You can also call it by name with
|
|
141
|
+
`/coderook`.
|
|
142
|
+
|
|
143
|
+
`coderook skill --project` writes it into `./.claude` instead of your home
|
|
144
|
+
folder, so it travels with the repository and everybody who clones it has it
|
|
145
|
+
too.
|
|
146
|
+
|
|
147
|
+
Sign in once per machine first — `coderook sign-in` — or set `CODEROOK_TOKEN`
|
|
148
|
+
in automation. The skill will not save anything without asking you first, and
|
|
149
|
+
never deletes.
|
|
150
|
+
|
|
151
|
+
## Installing it as a plugin instead
|
|
152
|
+
|
|
153
|
+
If you would rather not install the command line first, CodeRook publishes a
|
|
154
|
+
plugin marketplace of its own:
|
|
155
|
+
|
|
156
|
+
```
|
|
157
|
+
/plugin marketplace add https://coderook.com/marketplace.json
|
|
158
|
+
/plugin install coderook@coderook
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
That fetches the same package from npm and brings the skill with it. The
|
|
162
|
+
marketplace is a plain file on coderook.com rather than a git repository, so
|
|
163
|
+
there is no repository to clone and no second account anywhere.
|
|
164
|
+
|
|
165
|
+
One thing to know: a plugin install puts the package in Claude Code's plugin
|
|
166
|
+
folder, not on your `PATH`. The skill copes with that on its own by falling
|
|
167
|
+
back to `npx -y @coderook/cli`, but `npm install --global @coderook/cli` is
|
|
168
|
+
faster if you plan to run commands yourself as well.
|
|
169
|
+
|
|
170
|
+
## A structured connection instead
|
|
171
|
+
|
|
172
|
+
The skill teaches Claude the command line, which needs no configuration. If you
|
|
173
|
+
would rather it had structured tools, there is an MCP server as well. It works
|
|
174
|
+
with Codex too, which is the reason it exists.
|
|
129
175
|
|
|
130
176
|
Claude Code:
|
|
131
177
|
|
|
132
178
|
```
|
|
133
|
-
claude mcp add coderook -- coderook mcp
|
|
179
|
+
claude mcp add --scope user coderook -- npx -y @coderook/cli mcp
|
|
134
180
|
```
|
|
135
181
|
|
|
136
182
|
Codex, in `~/.codex/config.toml`:
|
|
137
183
|
|
|
138
184
|
```toml
|
|
139
185
|
[mcp_servers.coderook]
|
|
140
|
-
command = "
|
|
141
|
-
args = ["mcp"]
|
|
186
|
+
command = "npx"
|
|
187
|
+
args = ["-y", "@coderook/cli", "mcp"]
|
|
142
188
|
```
|
|
143
189
|
|
|
144
|
-
|
|
145
|
-
the files in a version, one file's contents at a version, and what has changed
|
|
146
|
-
in a local folder.
|
|
190
|
+
To share it with everyone on a project, commit this beside the code:
|
|
147
191
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
192
|
+
```json
|
|
193
|
+
// .mcp.json
|
|
194
|
+
{
|
|
195
|
+
"mcpServers": {
|
|
196
|
+
"coderook": {
|
|
197
|
+
"command": "npx",
|
|
198
|
+
"args": ["-y", "@coderook/cli", "mcp"]
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
```
|
|
152
203
|
|
|
153
|
-
|
|
154
|
-
|
|
204
|
+
It offers five things, all of which read: your projects, a project's versions,
|
|
205
|
+
the files in a version, one file's contents at a version, and what has changed
|
|
206
|
+
in a local folder. It cannot save, delete, or sign in or out.
|
|
155
207
|
|
|
156
208
|
## Checking things
|
|
157
209
|
|
package/dist/cli/src/cli.js
CHANGED
|
@@ -26,6 +26,7 @@ const progress_js_1 = require("./progress.js");
|
|
|
26
26
|
const project_commands_js_1 = require("./project_commands.js");
|
|
27
27
|
const track_commands_js_1 = require("./track_commands.js");
|
|
28
28
|
const mcp_js_1 = require("./mcp.js");
|
|
29
|
+
const skill_command_js_1 = require("./skill_command.js");
|
|
29
30
|
const service_commands_js_1 = require("./service_commands.js");
|
|
30
31
|
const worktree_js_1 = require("../../desktop-app/src/main/worktree.js");
|
|
31
32
|
const upload_js_1 = require("../../desktop-app/src/main/upload.js");
|
|
@@ -1444,6 +1445,38 @@ const SPECS = [
|
|
|
1444
1445
|
examples: ["coderook runner my-game --labels windows,signing"],
|
|
1445
1446
|
run: commandRunner,
|
|
1446
1447
|
},
|
|
1448
|
+
{
|
|
1449
|
+
/*
|
|
1450
|
+
The other way to hand this to an assistant is a plugin marketplace,
|
|
1451
|
+
and marketplaces live in git repositories — an odd thing to require
|
|
1452
|
+
of people using a version host that is deliberately not git. A skill
|
|
1453
|
+
is a file in a folder, and this already knows where the folder is.
|
|
1454
|
+
*/
|
|
1455
|
+
name: "skill",
|
|
1456
|
+
group: "Other",
|
|
1457
|
+
summary: "teach Claude Code about CodeRook",
|
|
1458
|
+
usage: "skill [--project]",
|
|
1459
|
+
detail: "Writes a short guide into Claude Code's skills folder, so it knows what\n" +
|
|
1460
|
+
"CodeRook is and how to drive it. After that you ask for it in words:\n" +
|
|
1461
|
+
"'save this to CodeRook', 'what changed?' — or run it by name with\n" +
|
|
1462
|
+
"/coderook.\n" +
|
|
1463
|
+
"\n" +
|
|
1464
|
+
"Personal by default, so it is there in every project on this machine.\n" +
|
|
1465
|
+
"--project writes it into ./.claude instead, where it travels with the\n" +
|
|
1466
|
+
"repository for everybody who clones it.\n" +
|
|
1467
|
+
"\n" +
|
|
1468
|
+
"It teaches the command line rather than the MCP server, because that\n" +
|
|
1469
|
+
"needs no configuration at all. `coderook mcp` is still there when a\n" +
|
|
1470
|
+
"structured connection is wanted.\n",
|
|
1471
|
+
options: [
|
|
1472
|
+
{
|
|
1473
|
+
flags: "--project",
|
|
1474
|
+
description: "install into this project rather than for you",
|
|
1475
|
+
},
|
|
1476
|
+
],
|
|
1477
|
+
examples: ["coderook skill", "coderook skill --project"],
|
|
1478
|
+
run: skill_command_js_1.commandSkill,
|
|
1479
|
+
},
|
|
1447
1480
|
{
|
|
1448
1481
|
/*
|
|
1449
1482
|
The assistants speak one protocol between them, so this is one server
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Teaching an assistant about CodeRook, in one command.
|
|
4
|
+
*
|
|
5
|
+
* The other route in is a plugin marketplace, and marketplaces live in git
|
|
6
|
+
* repositories — which is a strange thing to require of people using a version
|
|
7
|
+
* host that is deliberately not git, and stranger still when the recommended
|
|
8
|
+
* host is the one this product exists as an alternative to.
|
|
9
|
+
*
|
|
10
|
+
* A skill is a file in a folder. The command line is already installed and
|
|
11
|
+
* already knows where that folder is, so it writes it: no marketplace, no
|
|
12
|
+
* clone, no second account. `coderook skill` and the assistant knows what
|
|
13
|
+
* CodeRook is and how to drive it from then on, in every project.
|
|
14
|
+
*
|
|
15
|
+
* The skill teaches it the command line rather than the MCP server. Both work,
|
|
16
|
+
* but the command line needs no configuration at all — the assistant already
|
|
17
|
+
* has a shell, and a thing that works with nothing to set up is the thing most
|
|
18
|
+
* people should be offered first.
|
|
19
|
+
*/
|
|
20
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
21
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
22
|
+
};
|
|
23
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
24
|
+
exports.commandSkill = commandSkill;
|
|
25
|
+
const promises_1 = require("node:fs/promises");
|
|
26
|
+
const node_os_1 = __importDefault(require("node:os"));
|
|
27
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
28
|
+
const node_process_1 = __importDefault(require("node:process"));
|
|
29
|
+
const dim = (value) => `[2m${value}[0m`;
|
|
30
|
+
const bold = (value) => `[1m${value}[0m`;
|
|
31
|
+
const red = (value) => `[31m${value}[0m`;
|
|
32
|
+
const green = (value) => `[32m${value}[0m`;
|
|
33
|
+
const accent = (value) => `[33m${value}[0m`;
|
|
34
|
+
/**
|
|
35
|
+
* The skill as it ships, found relative to this file.
|
|
36
|
+
*
|
|
37
|
+
* Not read from the working directory, because the command is run from
|
|
38
|
+
* somebody's project and the file lives wherever npm put the package.
|
|
39
|
+
*/
|
|
40
|
+
async function shipped() {
|
|
41
|
+
/*
|
|
42
|
+
`__dirname` rather than `import.meta.url`: this package compiles to
|
|
43
|
+
CommonJS, where the latter is not available and the build refuses it.
|
|
44
|
+
*/
|
|
45
|
+
const here = __dirname;
|
|
46
|
+
/*
|
|
47
|
+
Compiled to dist/cli/src, so the package root is three levels up. Tried in
|
|
48
|
+
order rather than assumed, so running from source works as well as running
|
|
49
|
+
from an install.
|
|
50
|
+
*/
|
|
51
|
+
const candidates = [
|
|
52
|
+
node_path_1.default.resolve(here, "../../../skills/coderook/SKILL.md"),
|
|
53
|
+
node_path_1.default.resolve(here, "../../skills/coderook/SKILL.md"),
|
|
54
|
+
node_path_1.default.resolve(here, "../skills/coderook/SKILL.md"),
|
|
55
|
+
];
|
|
56
|
+
for (const candidate of candidates) {
|
|
57
|
+
try {
|
|
58
|
+
return await (0, promises_1.readFile)(candidate, "utf8");
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
/* try the next shape */
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
throw new Error("The skill file is missing from this installation.");
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Where the assistant looks.
|
|
68
|
+
*
|
|
69
|
+
* Personal by default — somebody who installs this wants it in every project,
|
|
70
|
+
* not only the one they happened to be standing in. `--project` writes it
|
|
71
|
+
* beside the code instead, which is what to do when it should travel with the
|
|
72
|
+
* repository for everybody who clones it.
|
|
73
|
+
*/
|
|
74
|
+
function destination(project) {
|
|
75
|
+
const root = project
|
|
76
|
+
? node_path_1.default.join(node_process_1.default.cwd(), ".claude")
|
|
77
|
+
: node_path_1.default.join(node_os_1.default.homedir(), ".claude");
|
|
78
|
+
return node_path_1.default.join(root, "skills", "coderook", "SKILL.md");
|
|
79
|
+
}
|
|
80
|
+
async function commandSkill(parsed) {
|
|
81
|
+
const project = parsed.flags.has("project");
|
|
82
|
+
const target = destination(project);
|
|
83
|
+
let body;
|
|
84
|
+
try {
|
|
85
|
+
body = await shipped();
|
|
86
|
+
}
|
|
87
|
+
catch (error) {
|
|
88
|
+
console.error(red(error instanceof Error ? error.message : String(error)));
|
|
89
|
+
return 1;
|
|
90
|
+
}
|
|
91
|
+
/*
|
|
92
|
+
Overwritten without asking, because the file is ours and the only reason it
|
|
93
|
+
differs is that it is an older copy. Anything a person wrote themselves
|
|
94
|
+
belongs under a different name.
|
|
95
|
+
*/
|
|
96
|
+
await (0, promises_1.mkdir)(node_path_1.default.dirname(target), { recursive: true });
|
|
97
|
+
await (0, promises_1.writeFile)(target, body, "utf8");
|
|
98
|
+
console.log(green("Installed the CodeRook skill."));
|
|
99
|
+
console.log(dim(" ") + target);
|
|
100
|
+
console.log("");
|
|
101
|
+
console.log(project
|
|
102
|
+
? dim("Everyone who opens this project in Claude Code now has it.")
|
|
103
|
+
: dim("Claude Code has it in every project on this machine."));
|
|
104
|
+
console.log("");
|
|
105
|
+
console.log(dim("Ask for it in words — ") + accent("“save this to CodeRook”"));
|
|
106
|
+
console.log(dim("or run it by name — ") + accent("/coderook"));
|
|
107
|
+
console.log("");
|
|
108
|
+
console.log(dim("Signed in? ") +
|
|
109
|
+
bold("coderook whoami") +
|
|
110
|
+
dim(" · if not: ") +
|
|
111
|
+
bold("coderook sign-in"));
|
|
112
|
+
return 0;
|
|
113
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@coderook/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.0",
|
|
4
4
|
"description": "CodeRook from the command line, on any operating system",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE.txt",
|
|
6
6
|
"homepage": "https://coderook.com",
|
|
@@ -20,14 +20,16 @@
|
|
|
20
20
|
"coderook": "dist/cli/src/cli.js"
|
|
21
21
|
},
|
|
22
22
|
"files": [
|
|
23
|
-
"
|
|
23
|
+
".claude-plugin",
|
|
24
|
+
"dist",
|
|
25
|
+
"skills"
|
|
24
26
|
],
|
|
25
27
|
"scripts": {
|
|
26
28
|
"build": "tsc -p tsconfig.json",
|
|
27
29
|
"check": "tsc -p tsconfig.json --noEmit",
|
|
28
30
|
"test": "tsc -p tsconfig.json && node --test --experimental-strip-types test/*.test.ts",
|
|
29
31
|
"start": "node dist/cli/src/cli.js",
|
|
30
|
-
"prepublishOnly": "npm run build",
|
|
32
|
+
"prepublishOnly": "npm run sync:plugin && npm run build",
|
|
31
33
|
"test:e2e": "node test/e2e.mjs",
|
|
32
34
|
"test:matrix": "node test/state-matrix.mjs",
|
|
33
35
|
"test:attempt": "node test/attempt-identity.mjs",
|
|
@@ -39,7 +41,8 @@
|
|
|
39
41
|
"test:floor": "node test/version-floor.mjs",
|
|
40
42
|
"test:faultsubmit": "node test/fault-submit.mjs",
|
|
41
43
|
"test:race": "node test/race-attempts.mjs",
|
|
42
|
-
"test:runner": "node test/runner-live.mjs"
|
|
44
|
+
"test:runner": "node test/runner-live.mjs",
|
|
45
|
+
"sync:plugin": "node scripts/sync-plugin-version.mjs"
|
|
43
46
|
},
|
|
44
47
|
"devDependencies": {
|
|
45
48
|
"@types/node": "24.10.1",
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: coderook
|
|
3
|
+
description: Save, browse and restore versions of a project on CodeRook — a version host where every version is a complete snapshot and there is no git to learn. Use when asked to save or submit work to CodeRook, check what has changed, look at a project's versions or files, fetch a project, start or switch a line of work, or resolve a save that landed at the same time as somebody else's.
|
|
4
|
+
allowed-tools: Bash Read
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# CodeRook
|
|
8
|
+
|
|
9
|
+
CodeRook stores whole snapshots of a folder. A version names every file the
|
|
10
|
+
project had at that moment, so restoring one never depends on the versions
|
|
11
|
+
around it, and there is no staging area, no branches to rebase and no history
|
|
12
|
+
to rewrite.
|
|
13
|
+
|
|
14
|
+
Everything here is the `coderook` command line. Run it with Bash.
|
|
15
|
+
|
|
16
|
+
## Before anything else
|
|
17
|
+
|
|
18
|
+
`coderook whoami` says who this machine is signed in as. If it refuses, the
|
|
19
|
+
person needs to run `coderook sign-in` themselves — it takes a personal access
|
|
20
|
+
token, so do not attempt it on their behalf.
|
|
21
|
+
|
|
22
|
+
If the command is not found at all, this machine has the skill but not the
|
|
23
|
+
command line. Either install it once with
|
|
24
|
+
`npm install --global @coderook/cli`, or put `npx -y @coderook/cli` where
|
|
25
|
+
`coderook` appears below — the commands are identical, npx is just slower
|
|
26
|
+
to start.
|
|
27
|
+
|
|
28
|
+
## Reading
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
coderook status # what has changed in this folder since its last version
|
|
32
|
+
coderook projects # every project on the account
|
|
33
|
+
coderook versions [project] # what has been saved, newest first
|
|
34
|
+
coderook tracks [project] # the lines a project has, and any waiting on a decision
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
`status` is the one to reach for when somebody asks what is uncommitted, what
|
|
38
|
+
changed, or whether anything needs saving. It reads the folder and changes
|
|
39
|
+
nothing.
|
|
40
|
+
|
|
41
|
+
## Saving
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
coderook submit -m "What changed, in a sentence"
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Send only what changed; the version still names every file. Write the message
|
|
48
|
+
yourself from the actual diff rather than asking for one — a message like
|
|
49
|
+
"update" helps nobody reading the history later.
|
|
50
|
+
|
|
51
|
+
**Ask before running this.** Saving is the one thing here that leaves a mark on
|
|
52
|
+
somebody's account, and a version saved by mistake is a version they have to
|
|
53
|
+
explain. Propose it, say what it would send, and let them agree.
|
|
54
|
+
|
|
55
|
+
`coderook submit -n` shows exactly what would be sent without sending it. Use
|
|
56
|
+
that freely; it is safe and it is the honest way to answer "what would this
|
|
57
|
+
upload?".
|
|
58
|
+
|
|
59
|
+
## Fetching
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
coderook get # bring this folder up to date
|
|
63
|
+
coderook clone <project> [dir] # fetch a project into a new folder
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
`get` protects local edits by default. `coderook get --replace` discards them
|
|
67
|
+
to make an exact copy — only run it when the person has said so in those terms.
|
|
68
|
+
|
|
69
|
+
## Lines of work
|
|
70
|
+
|
|
71
|
+
A project can have more than one line, so two people can save without one
|
|
72
|
+
landing on top of the other.
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
coderook track # which line this folder saves to
|
|
76
|
+
coderook track spike --new # start a line and switch to it
|
|
77
|
+
coderook track main # switch back
|
|
78
|
+
coderook submit --track spike -m "…"
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Switching says where the next save goes and nothing else — no files move.
|
|
82
|
+
Run `coderook get` afterwards to bring that line's files in.
|
|
83
|
+
|
|
84
|
+
## When two saves collide
|
|
85
|
+
|
|
86
|
+
If somebody saved while this folder was behind, the second save becomes a merge
|
|
87
|
+
waiting on a decision rather than overwriting anything.
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
coderook merges # this folder's saves waiting on a decision
|
|
91
|
+
coderook merge <ref> # look at one, and decide
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Read the conflict out to the person and let them choose. Do not pick a side for
|
|
95
|
+
them: the whole reason it stopped is that the service could not tell which copy
|
|
96
|
+
was wanted.
|
|
97
|
+
|
|
98
|
+
## What is worth leaving out
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
coderook ignore --suggest # dependency directories, build output, virtual environments
|
|
102
|
+
coderook ignore --suggest --apply # add the confident ones to .gitignore
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Rules live in the project's own `.gitignore`, so CodeRook, git and the website
|
|
106
|
+
all read one file.
|
|
107
|
+
|
|
108
|
+
## Bundles
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
coderook bundle . project.cbx # the whole project, every version, as one file
|
|
112
|
+
coderook inspect project.cbx # what is inside, without unpacking
|
|
113
|
+
coderook unbundle project.cbx ./restored
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## Things not to do
|
|
117
|
+
|
|
118
|
+
- Do not run `coderook delete` unless the person has asked for that project to
|
|
119
|
+
be deleted, by name, in this conversation.
|
|
120
|
+
- Do not run `coderook sign-out`. It costs them a token they have to fetch
|
|
121
|
+
again and gains nothing.
|
|
122
|
+
- Do not guess a project name. `coderook projects` lists them; a folder that is
|
|
123
|
+
already linked needs no name at all.
|
|
124
|
+
|
|
125
|
+
## If something refuses
|
|
126
|
+
|
|
127
|
+
The commands explain themselves — pass the message on rather than rewording it.
|
|
128
|
+
A refusal that says a token was not accepted means signing in; one that says a
|
|
129
|
+
project does not allow machines means its owner turned that off deliberately,
|
|
130
|
+
and the answer is to say so, not to find another route in.
|