@coderook/cli 0.15.0 → 0.17.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 +18 -0
- package/README.md +19 -0
- package/dist/cli/src/cli.js +25 -0
- package/dist/desktop-app/src/main/worktree.js +105 -2
- package/package.json +5 -3
- package/skills/coderook/SKILL.md +6 -0
|
@@ -0,0 +1,18 @@
|
|
|
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.17.0",
|
|
6
|
+
"author": {
|
|
7
|
+
"name": "ACCA Gaming Productions",
|
|
8
|
+
"url": "https://coderook.com"
|
|
9
|
+
},
|
|
10
|
+
"homepage": "https://coderook.com/docs#assistants",
|
|
11
|
+
"license": "SEE LICENSE IN LICENSE.txt",
|
|
12
|
+
"keywords": [
|
|
13
|
+
"coderook",
|
|
14
|
+
"versioning",
|
|
15
|
+
"snapshots",
|
|
16
|
+
"backup"
|
|
17
|
+
]
|
|
18
|
+
}
|
package/README.md
CHANGED
|
@@ -148,6 +148,25 @@ Sign in once per machine first — `coderook sign-in` — or set `CODEROOK_TOKEN
|
|
|
148
148
|
in automation. The skill will not save anything without asking you first, and
|
|
149
149
|
never deletes.
|
|
150
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
|
+
|
|
151
170
|
## A structured connection instead
|
|
152
171
|
|
|
153
172
|
The skill teaches Claude the command line, which needs no configuration. If you
|
package/dist/cli/src/cli.js
CHANGED
|
@@ -352,6 +352,31 @@ async function commandSubmit(parsed) {
|
|
|
352
352
|
const secrets = await (0, worktree_js_1.detectSecrets)(folder);
|
|
353
353
|
const sending = new Set(files.map((file) => file.path));
|
|
354
354
|
const exposed = secrets.filter((secret) => sending.has(secret));
|
|
355
|
+
/*
|
|
356
|
+
Whole folders that belong to a program rather than to the work.
|
|
357
|
+
|
|
358
|
+
The credential check answers by name, one file at a time, and cannot see
|
|
359
|
+
into a dependency folder at all — which is where a real cached login sat
|
|
360
|
+
while a project went up carrying a browser profile, an emulated database
|
|
361
|
+
and an unrelated work tree. Naming the folder is the only answer that
|
|
362
|
+
keeps working after the program next runs and rewrites what is inside it.
|
|
363
|
+
*/
|
|
364
|
+
const shielded = (await (0, worktree_js_1.detectPrivateDirectories)(folder)).filter((finding) => [...sending].some((file) => file === finding.path || file.startsWith(`${finding.path}/`)));
|
|
365
|
+
if (shielded.length && !hasFlag(parsed, "allow-private")) {
|
|
366
|
+
console.log(red(`
|
|
367
|
+
${shielded.length} folder${shielded.length === 1 ? "" : "s"} here belong${shielded.length === 1 ? "s" : ""} to a program, not to your project:`));
|
|
368
|
+
for (const finding of shielded) {
|
|
369
|
+
console.log(` ${finding.path} ${dim(`— ${finding.because}`)}`);
|
|
370
|
+
}
|
|
371
|
+
console.log(`
|
|
372
|
+
Nothing was sent. To leave them behind:`);
|
|
373
|
+
for (const finding of shielded) {
|
|
374
|
+
console.log(` ${accent(`echo "${finding.rule}" >> .gitignore`)}`);
|
|
375
|
+
}
|
|
376
|
+
console.error(`
|
|
377
|
+
Or pass ${accent("--allow-private")} if they genuinely belong in the project.`);
|
|
378
|
+
return 1;
|
|
379
|
+
}
|
|
355
380
|
if (exposed.length) {
|
|
356
381
|
console.log(red(`\n${exposed.length} file${exposed.length === 1 ? " looks like a credential" : "s look like credentials"}:`));
|
|
357
382
|
for (const secret of exposed.slice(0, 20))
|
|
@@ -18,6 +18,7 @@ exports.fileSizes = fileSizes;
|
|
|
18
18
|
exports.fileDiff = fileDiff;
|
|
19
19
|
exports.projectTree = projectTree;
|
|
20
20
|
exports.evaluateRules = evaluateRules;
|
|
21
|
+
exports.detectPrivateDirectories = detectPrivateDirectories;
|
|
21
22
|
exports.detectSecrets = detectSecrets;
|
|
22
23
|
/** Reading a project folder: changed files, diffs, and rule measurement. */
|
|
23
24
|
const node_child_process_1 = require("node:child_process");
|
|
@@ -773,8 +774,110 @@ function emptyEvaluation(truncated) {
|
|
|
773
774
|
truncated,
|
|
774
775
|
};
|
|
775
776
|
}
|
|
776
|
-
|
|
777
|
-
|
|
777
|
+
/*
|
|
778
|
+
Names that are a credential whatever is inside them.
|
|
779
|
+
|
|
780
|
+
The first three were the whole list, which meant the check answered for a
|
|
781
|
+
hand-written `.env` and for nothing a tool leaves behind. Every addition
|
|
782
|
+
below is something a program writes without being asked: a cloud CLI's
|
|
783
|
+
cached login, an SSH key, a registry token. Those are the ones that get
|
|
784
|
+
published, precisely because nobody chose to put them there.
|
|
785
|
+
*/
|
|
786
|
+
const SECRET_NAMES = new Set([
|
|
787
|
+
".env",
|
|
788
|
+
".env.local",
|
|
789
|
+
".env.production",
|
|
790
|
+
".npmrc",
|
|
791
|
+
".netrc",
|
|
792
|
+
".git-credentials",
|
|
793
|
+
".dockercfg",
|
|
794
|
+
".pypirc",
|
|
795
|
+
"credentials",
|
|
796
|
+
"credentials.json",
|
|
797
|
+
"wrangler-account.json",
|
|
798
|
+
"id_rsa",
|
|
799
|
+
"id_dsa",
|
|
800
|
+
"id_ecdsa",
|
|
801
|
+
"id_ed25519",
|
|
802
|
+
]);
|
|
803
|
+
const SECRET_SUFFIXES = [".pem", ".key", ".p12", ".pfx", ".keystore", ".jks"];
|
|
804
|
+
/*
|
|
805
|
+
Directories that are one program's private state, not somebody's project.
|
|
806
|
+
|
|
807
|
+
These are judged by directory rather than by file because that is how they
|
|
808
|
+
arrive: nobody adds `Local Storage/leveldb/000005.ldb`, they add a folder
|
|
809
|
+
and everything under it comes too. Matching the folder is also what lets
|
|
810
|
+
the answer be a rule — one line in .gitignore covers the lot, where a list
|
|
811
|
+
of the files inside it would go stale the moment the program ran again.
|
|
812
|
+
*/
|
|
813
|
+
const PRIVATE_DIRECTORIES = [
|
|
814
|
+
{
|
|
815
|
+
match: /^(Local Storage|Session Storage|IndexedDB|Service Worker|Cache|Code Cache|GPUCache)$/i,
|
|
816
|
+
because: "a browser profile, which holds the sites you are signed in to",
|
|
817
|
+
},
|
|
818
|
+
{ match: /^\.wrangler$/i, because: "local service state, including emulated databases" },
|
|
819
|
+
{ match: /^\.aws$/i, because: "cloud credentials" },
|
|
820
|
+
{ match: /^\.ssh$/i, because: "SSH keys" },
|
|
821
|
+
{ match: /^\.gnupg$/i, because: "signing keys" },
|
|
822
|
+
{ match: /^\.docker$/i, because: "registry logins" },
|
|
823
|
+
{ match: /^\.terraform$/i, because: "infrastructure state" },
|
|
824
|
+
/*
|
|
825
|
+
Not private in itself, and included for a reason worth stating: the
|
|
826
|
+
credential check refuses to walk it, sensibly, because it is enormous and
|
|
827
|
+
none of it is yours. That makes it the one place a cached login can sit
|
|
828
|
+
and never be found by name — which is exactly where a real one was, at
|
|
829
|
+
`node_modules/.cache/wrangler/wrangler-account.json`. Naming the folder
|
|
830
|
+
covers everything the name check cannot reach into.
|
|
831
|
+
*/
|
|
832
|
+
{
|
|
833
|
+
match: /^node_modules$/i,
|
|
834
|
+
because: "a dependency folder, which is rebuilt from your manifest and is where tools cache their logins",
|
|
835
|
+
},
|
|
836
|
+
];
|
|
837
|
+
/**
|
|
838
|
+
* Folders in this project that belong to a program rather than to the work.
|
|
839
|
+
*
|
|
840
|
+
* Deliberately separate from `detectSecrets`. A credential is a refusal: it
|
|
841
|
+
* must not be published and the answer is to remove it. One of these is a
|
|
842
|
+
* question — the files are not dangerous in themselves, they are simply not
|
|
843
|
+
* the project, and the useful response is a rule rather than a refusal.
|
|
844
|
+
*/
|
|
845
|
+
async function detectPrivateDirectories(root) {
|
|
846
|
+
const found = [];
|
|
847
|
+
const pending = [root];
|
|
848
|
+
let seen = 0;
|
|
849
|
+
while (pending.length && found.length < 25 && seen < 40_000) {
|
|
850
|
+
const directory = pending.pop();
|
|
851
|
+
let entries;
|
|
852
|
+
try {
|
|
853
|
+
entries = await (0, promises_1.readdir)(directory, { withFileTypes: true });
|
|
854
|
+
}
|
|
855
|
+
catch {
|
|
856
|
+
continue;
|
|
857
|
+
}
|
|
858
|
+
for (const entry of entries) {
|
|
859
|
+
seen += 1;
|
|
860
|
+
if (!entry.isDirectory())
|
|
861
|
+
continue;
|
|
862
|
+
if ((0, rules_js_1.isUncounted)(entry.name))
|
|
863
|
+
continue;
|
|
864
|
+
const full = node_path_1.default.join(directory, entry.name);
|
|
865
|
+
const relative = node_path_1.default.relative(root, full).split(node_path_1.default.sep).join("/");
|
|
866
|
+
const rule = PRIVATE_DIRECTORIES.find((candidate) => candidate.match.test(entry.name));
|
|
867
|
+
if (rule) {
|
|
868
|
+
found.push({
|
|
869
|
+
path: relative,
|
|
870
|
+
because: rule.because,
|
|
871
|
+
rule: `${relative}/`,
|
|
872
|
+
});
|
|
873
|
+
/* No need to walk into it; the whole folder is the answer. */
|
|
874
|
+
continue;
|
|
875
|
+
}
|
|
876
|
+
pending.push(full);
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
return found;
|
|
880
|
+
}
|
|
778
881
|
/** Files the flow must ask about before the first upload. */
|
|
779
882
|
async function detectSecrets(root) {
|
|
780
883
|
const found = [];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@coderook/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.17.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,6 +20,7 @@
|
|
|
20
20
|
"coderook": "dist/cli/src/cli.js"
|
|
21
21
|
},
|
|
22
22
|
"files": [
|
|
23
|
+
".claude-plugin",
|
|
23
24
|
"dist",
|
|
24
25
|
"skills"
|
|
25
26
|
],
|
|
@@ -28,7 +29,7 @@
|
|
|
28
29
|
"check": "tsc -p tsconfig.json --noEmit",
|
|
29
30
|
"test": "tsc -p tsconfig.json && node --test --experimental-strip-types test/*.test.ts",
|
|
30
31
|
"start": "node dist/cli/src/cli.js",
|
|
31
|
-
"prepublishOnly": "npm run build",
|
|
32
|
+
"prepublishOnly": "npm run sync:plugin && npm run build",
|
|
32
33
|
"test:e2e": "node test/e2e.mjs",
|
|
33
34
|
"test:matrix": "node test/state-matrix.mjs",
|
|
34
35
|
"test:attempt": "node test/attempt-identity.mjs",
|
|
@@ -40,7 +41,8 @@
|
|
|
40
41
|
"test:floor": "node test/version-floor.mjs",
|
|
41
42
|
"test:faultsubmit": "node test/fault-submit.mjs",
|
|
42
43
|
"test:race": "node test/race-attempts.mjs",
|
|
43
|
-
"test:runner": "node test/runner-live.mjs"
|
|
44
|
+
"test:runner": "node test/runner-live.mjs",
|
|
45
|
+
"sync:plugin": "node scripts/sync-plugin-version.mjs"
|
|
44
46
|
},
|
|
45
47
|
"devDependencies": {
|
|
46
48
|
"@types/node": "24.10.1",
|
package/skills/coderook/SKILL.md
CHANGED
|
@@ -19,6 +19,12 @@ Everything here is the `coderook` command line. Run it with Bash.
|
|
|
19
19
|
person needs to run `coderook sign-in` themselves — it takes a personal access
|
|
20
20
|
token, so do not attempt it on their behalf.
|
|
21
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
|
+
|
|
22
28
|
## Reading
|
|
23
29
|
|
|
24
30
|
```bash
|