@coderook/cli 0.37.0 → 0.38.1
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 +1 -1
- package/dist/cli/src/cli.js +48 -19
- package/dist/cli/src/runner.js +35 -1
- package/dist/cli/src/workspace_command.js +198 -0
- package/package.json +66 -66
- package/skills/coderook/SKILL.md +413 -403
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "coderook",
|
|
3
3
|
"displayName": "CodeRook",
|
|
4
4
|
"description": "Save, browse and restore whole-snapshot versions of a project on CodeRook, with cbx or plain git, from Claude Code.",
|
|
5
|
-
"version": "0.
|
|
5
|
+
"version": "0.38.1",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "ACCA Gaming Productions",
|
|
8
8
|
"url": "https://coderook.com"
|
package/dist/cli/src/cli.js
CHANGED
|
@@ -22,6 +22,7 @@ const node_crypto_1 = require("node:crypto");
|
|
|
22
22
|
const node_fs_1 = require("node:fs");
|
|
23
23
|
const node_path_1 = __importDefault(require("node:path"));
|
|
24
24
|
const node_process_1 = __importDefault(require("node:process"));
|
|
25
|
+
const workspace_command_js_1 = require("./workspace_command.js");
|
|
25
26
|
const api_js_1 = require("./api.js");
|
|
26
27
|
const registry_js_1 = require("./registry.js");
|
|
27
28
|
const help_js_1 = require("./help.js");
|
|
@@ -686,7 +687,7 @@ async function commandImport(parsed) {
|
|
|
686
687
|
flags: submitFlags,
|
|
687
688
|
});
|
|
688
689
|
if (code === 0 && plan.temporary) {
|
|
689
|
-
console.log(dim(`The working copy stays at ${plan.folder} until you remove it.
|
|
690
|
+
console.log(dim(`The working copy stays at ${plan.folder} until you remove it.
|
|
690
691
|
` +
|
|
691
692
|
`Run ${accent("cbx get " + plan.name)} anywhere to fetch it fresh.`));
|
|
692
693
|
}
|
|
@@ -972,17 +973,17 @@ async function commandSubmit(parsed) {
|
|
|
972
973
|
*/
|
|
973
974
|
const shielded = (await (0, worktree_js_1.detectPrivateDirectories)(folder)).filter((finding) => [...sending].some((file) => file === finding.path || file.startsWith(`${finding.path}/`)));
|
|
974
975
|
if (shielded.length && !hasFlag(parsed, "allow-private")) {
|
|
975
|
-
console.log(red(`
|
|
976
|
+
console.log(red(`
|
|
976
977
|
${shielded.length} folder${shielded.length === 1 ? "" : "s"} here belong${shielded.length === 1 ? "s" : ""} to a program, not to your project:`));
|
|
977
978
|
for (const finding of shielded) {
|
|
978
979
|
console.log(` ${finding.path} ${dim(`— ${finding.because}`)}`);
|
|
979
980
|
}
|
|
980
|
-
console.log(`
|
|
981
|
+
console.log(`
|
|
981
982
|
Nothing was sent. To leave them behind:`);
|
|
982
983
|
for (const finding of shielded) {
|
|
983
984
|
console.log(` ${accent(`echo "${finding.rule}" >> .gitignore`)}`);
|
|
984
985
|
}
|
|
985
|
-
console.error(`
|
|
986
|
+
console.error(`
|
|
986
987
|
Or pass ${accent("--allow-private")} if they genuinely belong in the project.`);
|
|
987
988
|
return 1;
|
|
988
989
|
}
|
|
@@ -1031,7 +1032,7 @@ Or pass ${accent("--allow-private")} if they genuinely belong in the project.`);
|
|
|
1031
1032
|
`rest were not opened.`));
|
|
1032
1033
|
}
|
|
1033
1034
|
if (stillPasted.length) {
|
|
1034
|
-
console.log(red(`
|
|
1035
|
+
console.log(red(`
|
|
1035
1036
|
${stillPasted.length} file${stillPasted.length === 1 ? " has a credential" : "s have credentials"} inside:`));
|
|
1036
1037
|
for (const finding of stillPasted.slice(0, 20)) {
|
|
1037
1038
|
console.log(` ${finding.path}`);
|
|
@@ -1046,11 +1047,11 @@ ${stillPasted.length} file${stillPasted.length === 1 ? " has a credential" : "s
|
|
|
1046
1047
|
console.log(dim(` …and ${stillPasted.length - 20} more`));
|
|
1047
1048
|
}
|
|
1048
1049
|
if (!hasFlag(parsed, "allow-secrets")) {
|
|
1049
|
-
console.error(`
|
|
1050
|
+
console.error(`
|
|
1050
1051
|
Nothing was sent. Move the key into an environment variable, and if` +
|
|
1051
1052
|
` it has ever been published, replace it at the service that issued` +
|
|
1052
1053
|
` it — a key that has leaked stays leaked.` +
|
|
1053
|
-
`
|
|
1054
|
+
`
|
|
1054
1055
|
Pass ${accent("--allow-secrets")} if these are not real keys.`);
|
|
1055
1056
|
return 1;
|
|
1056
1057
|
}
|
|
@@ -1257,7 +1258,7 @@ Pass ${accent("--allow-secrets")} if these are not real keys.`);
|
|
|
1257
1258
|
continue;
|
|
1258
1259
|
}
|
|
1259
1260
|
if (failure?.kind === "interrupted") {
|
|
1260
|
-
console.error(red(`
|
|
1261
|
+
console.error(red(`
|
|
1261
1262
|
The connection failed: ${text}`));
|
|
1262
1263
|
console.error(`Your work may already have been saved. Run the same command again —` +
|
|
1263
1264
|
` it will not create a second version.`);
|
|
@@ -1500,7 +1501,7 @@ async function commandGet(parsed) {
|
|
|
1500
1501
|
`changed while the interrupted fetch was stopped:`));
|
|
1501
1502
|
for (const file of changedInTheGap.slice(0, 20))
|
|
1502
1503
|
console.error(` ${file.path}`);
|
|
1503
|
-
console.error(`
|
|
1504
|
+
console.error(`
|
|
1504
1505
|
Save them with ${accent("cbx submit")}, or finish the fetch and ` +
|
|
1505
1506
|
`discard them with ${accent("cbx get --replace")}.`);
|
|
1506
1507
|
return 1;
|
|
@@ -1783,8 +1784,8 @@ async function suggestRules(folder, apply) {
|
|
|
1783
1784
|
const addition = (0, detect_js_1.rulesFromSuggestions)(recommended);
|
|
1784
1785
|
const shared = rules.shared.trimEnd();
|
|
1785
1786
|
await (0, worktree_js_1.writeRules)(folder, {
|
|
1786
|
-
shared: shared ? `${shared}
|
|
1787
|
-
|
|
1787
|
+
shared: shared ? `${shared}
|
|
1788
|
+
|
|
1788
1789
|
${addition}` : addition,
|
|
1789
1790
|
local: rules.local,
|
|
1790
1791
|
});
|
|
@@ -1989,7 +1990,7 @@ async function commandMerges(parsed) {
|
|
|
1989
1990
|
const counts = merge.conflicts;
|
|
1990
1991
|
console.log(`${accent(merge.reference)} ${counts ? `${counts.unresolved} of ${counts.total} still to decide` : ""} ${dim(new Date(merge.createdAt).toLocaleString())}`);
|
|
1991
1992
|
}
|
|
1992
|
-
console.log(dim(`
|
|
1993
|
+
console.log(dim(`
|
|
1993
1994
|
Run cbx merge <reference> to look at one.`));
|
|
1994
1995
|
return 0;
|
|
1995
1996
|
}
|
|
@@ -2092,7 +2093,7 @@ async function commandMerge(parsed) {
|
|
|
2092
2093
|
console.log(dim(` waiting on ${waiting.map((person) => person.displayName).join(", ")}`));
|
|
2093
2094
|
}
|
|
2094
2095
|
}
|
|
2095
|
-
console.log(`
|
|
2096
|
+
console.log(`
|
|
2096
2097
|
${accent(now.mergeTrack.reference)} · ` +
|
|
2097
2098
|
/*
|
|
2098
2099
|
The combined version is only counted once it can be built; until then
|
|
@@ -2111,7 +2112,7 @@ ${accent(now.mergeTrack.reference)} · ` +
|
|
|
2111
2112
|
return 1;
|
|
2112
2113
|
}
|
|
2113
2114
|
const applied = await (0, api_js_1.applyMerge)(summary.id);
|
|
2114
|
-
console.log(`
|
|
2115
|
+
console.log(`
|
|
2115
2116
|
Applied as ${accent(`v${applied.version.sequence}`)}.`);
|
|
2116
2117
|
if (applied.closedIssues?.length) {
|
|
2117
2118
|
console.log(`Closed ${applied.closedIssues.map((number) => `#${number}`).join(", ")}.`);
|
|
@@ -2121,13 +2122,13 @@ Applied as ${accent(`v${applied.version.sequence}`)}.`);
|
|
|
2121
2122
|
}
|
|
2122
2123
|
const stillOpen = now.conflicts.some((conflict) => !conflict.resolvedAt);
|
|
2123
2124
|
if (!decision && stillOpen) {
|
|
2124
|
-
console.log(dim(`
|
|
2125
|
+
console.log(dim(`
|
|
2125
2126
|
--mine keeps yours, --theirs keeps what was already saved,` +
|
|
2126
|
-
` --both keeps theirs and saves yours beside it, --drop removes the file.
|
|
2127
|
+
` --both keeps theirs and saves yours beside it, --drop removes the file.
|
|
2127
2128
|
Add --path <file> for one file, then --apply when ready.`));
|
|
2128
2129
|
}
|
|
2129
2130
|
else if (now.provisional.ready) {
|
|
2130
|
-
console.log(dim(`
|
|
2131
|
+
console.log(dim(`
|
|
2131
2132
|
Run cbx merge ${now.mergeTrack.reference} --apply to publish it.`));
|
|
2132
2133
|
}
|
|
2133
2134
|
return 0;
|
|
@@ -2713,6 +2714,33 @@ const SPECS = [
|
|
|
2713
2714
|
],
|
|
2714
2715
|
run: project_commands_js_1.commandVersions,
|
|
2715
2716
|
},
|
|
2717
|
+
{
|
|
2718
|
+
name: "workspace",
|
|
2719
|
+
group: "Your projects",
|
|
2720
|
+
summary: "edit, save and run a private project remotely without a checkout",
|
|
2721
|
+
usage: "workspace <open|list|show|read|files|search|diff|patch|save|run|logs|cancel|deploy|preview|handoff|close|recipe> <project> [workspace] [job]",
|
|
2722
|
+
detail: "Works on CodeRook's durable remote draft, never the current local folder. Use --data-file for structured edits or recipe configuration. Mutations need the reviewed --revision and a unique --key; reuse the same key and request when retrying. Recipes reference existing project workflows. Saving and deploying are separate operations.",
|
|
2723
|
+
options: [
|
|
2724
|
+
{ flags: "--data-file <path>", description: "read an operation's JSON from this local file" },
|
|
2725
|
+
{ flags: "--name <name>", description: "name a new workspace" },
|
|
2726
|
+
{ flags: "--track <track>", description: "base the workspace on this project Track" },
|
|
2727
|
+
{ flags: "--base-version <id>", description: "select a saved base version" },
|
|
2728
|
+
{ flags: "--revision <number>", description: "the exact reviewed workspace revision" },
|
|
2729
|
+
{ flags: "--key <operation-key>", description: "unique idempotency key; preserve it for retries" },
|
|
2730
|
+
{ flags: "--path <path>", description: "read this project-relative file" },
|
|
2731
|
+
{ flags: "--query <text>", description: "text to find in the remote workspace" },
|
|
2732
|
+
{ flags: "--offset <number>", description: "continue a paginated listing, search or diff" },
|
|
2733
|
+
{ flags: "--start-line <number>", description: "first text line to read" },
|
|
2734
|
+
{ flags: "--lines <number>", description: "maximum text lines to read" },
|
|
2735
|
+
{ flags: "--recipe <name>", description: "configured hosted execution recipe" },
|
|
2736
|
+
{ flags: "--target <target>", description: "exact configured deployment target" },
|
|
2737
|
+
{ flags: "--version <id>", description: "saved workspace version to deploy" },
|
|
2738
|
+
{ flags: "-m, --message <text>", description: "describe the version being saved" },
|
|
2739
|
+
{ flags: "--note <text>", description: "task context for another computer/assistant" },
|
|
2740
|
+
],
|
|
2741
|
+
examples: ["cbx workspace open my-project --name \"Laptop work\" --key laptop-open-001", "cbx workspace list my-project"],
|
|
2742
|
+
run: workspace_command_js_1.commandWorkspace,
|
|
2743
|
+
},
|
|
2716
2744
|
{
|
|
2717
2745
|
name: "ai",
|
|
2718
2746
|
group: "Your projects",
|
|
@@ -3670,7 +3698,8 @@ const SPECS = [
|
|
|
3670
3698
|
name: "mcp",
|
|
3671
3699
|
group: "Other",
|
|
3672
3700
|
summary: "serve CodeRook to Claude Code, Codex and other assistants",
|
|
3673
|
-
usage: "mcp",
|
|
3701
|
+
usage: "mcp [--workspaces]",
|
|
3702
|
+
options: [{ flags: "--workspaces", description: "opt into the remote connector's write/run/deploy tools and workspace permissions" }],
|
|
3674
3703
|
detail: "Speaks the Model Context Protocol on stdin and stdout, so an assistant\n" +
|
|
3675
3704
|
"can look at your projects while it works. Not run by hand — point the\n" +
|
|
3676
3705
|
"assistant at it and it starts and stops the process itself.\n" +
|
|
@@ -3691,7 +3720,7 @@ const SPECS = [
|
|
|
3691
3720
|
"A project whose owner has turned off machine reading is refused, in\n" +
|
|
3692
3721
|
"words rather than as a status code.\n",
|
|
3693
3722
|
examples: ["claude mcp add coderook -- cbx mcp"],
|
|
3694
|
-
run: ()
|
|
3723
|
+
run: parsed => parsed.flags.has("workspaces") ? (0, workspace_command_js_1.commandWorkspaceMcp)() : (0, mcp_js_1.commandMcp)(version_js_1.VERSION),
|
|
3695
3724
|
},
|
|
3696
3725
|
{
|
|
3697
3726
|
name: "doctor",
|
package/dist/cli/src/runner.js
CHANGED
|
@@ -32,6 +32,9 @@ exports.report = report;
|
|
|
32
32
|
const node_child_process_1 = require("node:child_process");
|
|
33
33
|
const node_crypto_1 = require("node:crypto");
|
|
34
34
|
const promises_1 = require("node:fs/promises");
|
|
35
|
+
const node_fs_1 = require("node:fs");
|
|
36
|
+
const node_stream_1 = require("node:stream");
|
|
37
|
+
const promises_2 = require("node:stream/promises");
|
|
35
38
|
const node_os_1 = require("node:os");
|
|
36
39
|
const node_os_2 = __importDefault(require("node:os"));
|
|
37
40
|
const node_path_1 = __importDefault(require("node:path"));
|
|
@@ -395,7 +398,38 @@ async function performRun(repositoryId, run, runner) {
|
|
|
395
398
|
}
|
|
396
399
|
});
|
|
397
400
|
}, HEARTBEAT_MS);
|
|
398
|
-
if (run.
|
|
401
|
+
if (run.workspaceInput) {
|
|
402
|
+
const input = await call(`/v1/repositories/${repositoryId}/workspace-runs/${run.runId}/input`);
|
|
403
|
+
run.command = input.command;
|
|
404
|
+
run.workingDirectory = input.workingDirectory;
|
|
405
|
+
run.artifactPaths = input.artifactPaths;
|
|
406
|
+
logs.add(`Fetching ${input.files.length} selected files from frozen workspace revision ${input.revision}…`, 'stdout');
|
|
407
|
+
const token = await (0, config_js_1.loadToken)();
|
|
408
|
+
for (const file of input.files) {
|
|
409
|
+
if (cancelled)
|
|
410
|
+
throw new Error('Cancelled while fetching workspace input');
|
|
411
|
+
if (!/^[a-f0-9]{64}$/.test(file.sha256 ?? ''))
|
|
412
|
+
throw new Error(`The frozen file ${file.path} has no verified content digest`);
|
|
413
|
+
const destination = insideWorkspace(workspace, file.path);
|
|
414
|
+
await (0, promises_1.mkdir)(node_path_1.default.dirname(destination), { recursive: true });
|
|
415
|
+
const response = await fetch(`${(0, config_js_1.apiOrigin)()}/v1/repositories/${repositoryId}/workspace-runs/${run.runId}/input/${file.index}`, { headers: { authorization: `Bearer ${token}`, ...(0, identify_js_1.clientHeaders)() }, signal: AbortSignal.timeout(REQUEST_DEADLINE_MS) });
|
|
416
|
+
if (!response.ok || !response.body)
|
|
417
|
+
throw new Error(`Could not fetch frozen workspace file ${file.path} (${response.status})`);
|
|
418
|
+
const hash = (0, node_crypto_1.createHash)('sha256');
|
|
419
|
+
let bytes = 0;
|
|
420
|
+
const verify = new node_stream_1.Transform({ transform(chunk, _encoding, callback) { bytes += chunk.length; if (bytes > file.size) {
|
|
421
|
+
callback(new Error('Frozen workspace file exceeds its declared size'));
|
|
422
|
+
return;
|
|
423
|
+
} hash.update(chunk); callback(null, chunk); } });
|
|
424
|
+
await (0, promises_2.pipeline)(node_stream_1.Readable.fromWeb(response.body), verify, (0, node_fs_1.createWriteStream)(destination));
|
|
425
|
+
if (bytes !== file.size || hash.digest('hex') !== file.sha256)
|
|
426
|
+
throw new Error(`Frozen content verification failed for ${file.path}; credential-bearing source must use separately configured credentials`);
|
|
427
|
+
if (file.executable && process.platform !== 'win32')
|
|
428
|
+
await (0, promises_1.chmod)(destination, 0o755);
|
|
429
|
+
}
|
|
430
|
+
logs.add('Selected workspace inputs verified.', 'stdout');
|
|
431
|
+
}
|
|
432
|
+
else if (run.versionId) {
|
|
399
433
|
logs.add(`Fetching v${run.versionSequence ?? "?"}…`, "stdout");
|
|
400
434
|
/*
|
|
401
435
|
The one phase that knows its own size. The version says how many files
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.commandWorkspace = commandWorkspace;
|
|
4
|
+
exports.commandWorkspaceMcp = commandWorkspaceMcp;
|
|
5
|
+
const promises_1 = require("node:fs/promises");
|
|
6
|
+
const node_readline_1 = require("node:readline");
|
|
7
|
+
const api_js_1 = require("./api.js");
|
|
8
|
+
const config_js_1 = require("./config.js");
|
|
9
|
+
async function commandWorkspace(parsed) {
|
|
10
|
+
const [operation, projectRef, workspace, runId] = parsed.positional;
|
|
11
|
+
if (!operation || !projectRef)
|
|
12
|
+
throw new Error("Use cbx workspace <operation> <project> [workspace]. See cbx help workspace.");
|
|
13
|
+
const project = await (0, api_js_1.findProject)(projectRef);
|
|
14
|
+
if (!project)
|
|
15
|
+
throw new Error(`No accessible project ${projectRef}`);
|
|
16
|
+
const root = `/v1/repositories/${encodeURIComponent(project.id)}`;
|
|
17
|
+
const flag = (name) => {
|
|
18
|
+
const value = parsed.flags.get(name);
|
|
19
|
+
return typeof value === "string" ? value : undefined;
|
|
20
|
+
};
|
|
21
|
+
const integer = (name) => {
|
|
22
|
+
const value = flag(name);
|
|
23
|
+
if (value === undefined)
|
|
24
|
+
return undefined;
|
|
25
|
+
const n = Number(value);
|
|
26
|
+
if (!Number.isSafeInteger(n) || n < 0)
|
|
27
|
+
throw new Error(`${name} must be a non-negative integer`);
|
|
28
|
+
return n;
|
|
29
|
+
};
|
|
30
|
+
let data = {};
|
|
31
|
+
const source = flag("data-file");
|
|
32
|
+
if (source) {
|
|
33
|
+
const raw = JSON.parse(await (0, promises_1.readFile)(source, "utf8"));
|
|
34
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw))
|
|
35
|
+
throw new Error("The data file must contain a JSON object");
|
|
36
|
+
data = raw;
|
|
37
|
+
}
|
|
38
|
+
const key = flag("key") ?? data.operationKey;
|
|
39
|
+
const revision = integer("revision") ?? data.expectedRevision;
|
|
40
|
+
let path = `${root}/workspaces`, method = "GET", body;
|
|
41
|
+
if (operation === "open") {
|
|
42
|
+
method = "POST";
|
|
43
|
+
body = {
|
|
44
|
+
...data,
|
|
45
|
+
name: flag("name") ?? data.name,
|
|
46
|
+
track: flag("track") ?? data.track,
|
|
47
|
+
baseVersionId: flag("base-version") ?? data.baseVersionId,
|
|
48
|
+
operationKey: key,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
else if (operation === "recipe") {
|
|
52
|
+
if (!workspace)
|
|
53
|
+
throw new Error("Name the recipe after the project");
|
|
54
|
+
path = `${root}/workspace-recipes/${encodeURIComponent(workspace)}`;
|
|
55
|
+
method = "PUT";
|
|
56
|
+
body = data;
|
|
57
|
+
}
|
|
58
|
+
else if (operation !== "list") {
|
|
59
|
+
if (!workspace)
|
|
60
|
+
throw new Error("Supply the remote workspace ID; this command does not use a local project folder");
|
|
61
|
+
path += `/${encodeURIComponent(workspace)}`;
|
|
62
|
+
if (operation === "read" || operation === "files") {
|
|
63
|
+
const q = new URLSearchParams();
|
|
64
|
+
for (const [name, value] of Object.entries({
|
|
65
|
+
path: flag("path"),
|
|
66
|
+
revision: integer("revision"),
|
|
67
|
+
startLine: integer("start-line"),
|
|
68
|
+
lines: integer("lines"),
|
|
69
|
+
offset: integer("offset"),
|
|
70
|
+
}))
|
|
71
|
+
if (value !== undefined)
|
|
72
|
+
q.set(name, String(value));
|
|
73
|
+
path += `/files?${q}`;
|
|
74
|
+
}
|
|
75
|
+
else if (operation === "search")
|
|
76
|
+
path += `/search?${new URLSearchParams({ q: flag("query") ?? "", offset: String(integer("offset") ?? 0) })}`;
|
|
77
|
+
else if (operation === "diff")
|
|
78
|
+
path += `/diff?offset=${integer("offset") ?? 0}`;
|
|
79
|
+
else if (operation === "patch") {
|
|
80
|
+
path += "/changes";
|
|
81
|
+
method = "POST";
|
|
82
|
+
body = { ...data, operationKey: key, expectedRevision: revision };
|
|
83
|
+
}
|
|
84
|
+
else if (operation === "save") {
|
|
85
|
+
path += "/save";
|
|
86
|
+
method = "POST";
|
|
87
|
+
body = {
|
|
88
|
+
...data,
|
|
89
|
+
operationKey: key,
|
|
90
|
+
expectedRevision: revision,
|
|
91
|
+
message: flag("message") ?? flag("m") ?? data.message,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
else if (operation === "run" ||
|
|
95
|
+
operation === "deploy" ||
|
|
96
|
+
operation === "preview") {
|
|
97
|
+
path += "/runs";
|
|
98
|
+
method = "POST";
|
|
99
|
+
body = {
|
|
100
|
+
...data,
|
|
101
|
+
operationKey: key,
|
|
102
|
+
expectedRevision: revision,
|
|
103
|
+
recipe: flag("recipe") ?? data.recipe,
|
|
104
|
+
...(operation === "deploy"
|
|
105
|
+
? {
|
|
106
|
+
target: flag("target") ?? data.target,
|
|
107
|
+
savedVersionId: flag("version") ?? data.savedVersionId,
|
|
108
|
+
}
|
|
109
|
+
: {}),
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
else if (operation === "logs" ||
|
|
113
|
+
operation === "runs" ||
|
|
114
|
+
operation === "cancel") {
|
|
115
|
+
if (!runId)
|
|
116
|
+
throw new Error("Supply the job ID after the workspace ID");
|
|
117
|
+
path += `/runs/${encodeURIComponent(runId)}`;
|
|
118
|
+
if (operation === "cancel") {
|
|
119
|
+
path += "/cancel";
|
|
120
|
+
method = "POST";
|
|
121
|
+
body = {};
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
else if (operation === "handoff" || operation === "close") {
|
|
125
|
+
method = "PATCH";
|
|
126
|
+
body = {
|
|
127
|
+
...data,
|
|
128
|
+
expectedRevision: revision,
|
|
129
|
+
note: flag("note") ?? data.note,
|
|
130
|
+
...(operation === "close" ? { archived: true } : {}),
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
else if (operation !== "show")
|
|
134
|
+
throw new Error(`Unknown workspace operation ${operation}`);
|
|
135
|
+
}
|
|
136
|
+
const result = await (0, api_js_1.callApi)(path, {
|
|
137
|
+
method,
|
|
138
|
+
...(body === undefined ? {} : { body }),
|
|
139
|
+
});
|
|
140
|
+
console.log(JSON.stringify(result, null, 2));
|
|
141
|
+
return 0;
|
|
142
|
+
}
|
|
143
|
+
/** Explicit remote/write-capable alternative; the original cbx mcp stays read-only. */
|
|
144
|
+
async function commandWorkspaceMcp() {
|
|
145
|
+
const lines = (0, node_readline_1.createInterface)({ input: process.stdin, crlfDelay: Infinity });
|
|
146
|
+
for await (const line of lines) {
|
|
147
|
+
if (!line.trim())
|
|
148
|
+
continue;
|
|
149
|
+
let request;
|
|
150
|
+
try {
|
|
151
|
+
if (Buffer.byteLength(line) > 2 * 1024 * 1024)
|
|
152
|
+
throw new Error("MCP request exceeds 2 MiB");
|
|
153
|
+
request = JSON.parse(line);
|
|
154
|
+
}
|
|
155
|
+
catch {
|
|
156
|
+
process.stdout.write(JSON.stringify({
|
|
157
|
+
jsonrpc: "2.0",
|
|
158
|
+
id: null,
|
|
159
|
+
error: { code: -32700, message: "Invalid or oversized JSON request" },
|
|
160
|
+
}) + "\n");
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
try {
|
|
164
|
+
const token = await (0, config_js_1.loadToken)();
|
|
165
|
+
if (!token)
|
|
166
|
+
throw new Error("Sign in with cbx sign-in before using remote workspace tools");
|
|
167
|
+
const response = await fetch(`${(0, config_js_1.apiOrigin)()}/mcp`, {
|
|
168
|
+
method: "POST",
|
|
169
|
+
headers: {
|
|
170
|
+
authorization: `Bearer ${token}`,
|
|
171
|
+
"content-type": "application/json",
|
|
172
|
+
},
|
|
173
|
+
body: line,
|
|
174
|
+
signal: AbortSignal.timeout(180000),
|
|
175
|
+
});
|
|
176
|
+
if (response.status === 202)
|
|
177
|
+
continue;
|
|
178
|
+
const output = await response.json();
|
|
179
|
+
if (!response.ok && request.id !== undefined)
|
|
180
|
+
throw new Error("CodeRook refused the MCP connection; check sign-in and workspace scopes");
|
|
181
|
+
process.stdout.write(JSON.stringify(output) + "\n");
|
|
182
|
+
}
|
|
183
|
+
catch (error) {
|
|
184
|
+
if (request.id !== undefined)
|
|
185
|
+
process.stdout.write(JSON.stringify({
|
|
186
|
+
jsonrpc: "2.0",
|
|
187
|
+
id: request.id,
|
|
188
|
+
error: {
|
|
189
|
+
code: -32001,
|
|
190
|
+
message: error instanceof Error
|
|
191
|
+
? error.message
|
|
192
|
+
: "Remote workspace request failed",
|
|
193
|
+
},
|
|
194
|
+
}) + "\n");
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
return 0;
|
|
198
|
+
}
|
package/package.json
CHANGED
|
@@ -1,66 +1,66 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@coderook/cli",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "The command line for CodeRook (coderook.com): version control and project hosting for projects of any size. Every save is a compressed snapshot, private or public, never used to train AI.",
|
|
5
|
-
"license": "Apache-2.0",
|
|
6
|
-
"homepage": "https://coderook.com",
|
|
7
|
-
"bugs": {
|
|
8
|
-
"url": "https://github.com/AlexC1991/cbx/issues"
|
|
9
|
-
},
|
|
10
|
-
"keywords": [
|
|
11
|
-
"coderook",
|
|
12
|
-
"cbx",
|
|
13
|
-
"version-control",
|
|
14
|
-
"vcs",
|
|
15
|
-
"versioning",
|
|
16
|
-
"project-hosting",
|
|
17
|
-
"backup",
|
|
18
|
-
"snapshots",
|
|
19
|
-
"large-files",
|
|
20
|
-
"gamedev"
|
|
21
|
-
],
|
|
22
|
-
"engines": {
|
|
23
|
-
"node": ">=22.15.0"
|
|
24
|
-
},
|
|
25
|
-
"bin": {
|
|
26
|
-
"cbx": "dist/cli/src/cli.js",
|
|
27
|
-
"coderook": "dist/cli/src/cli.js",
|
|
28
|
-
"git-remote-coderook": "dist/cli/src/git_remote_bin.js"
|
|
29
|
-
},
|
|
30
|
-
"files": [
|
|
31
|
-
".claude-plugin",
|
|
32
|
-
"dist",
|
|
33
|
-
"skills",
|
|
34
|
-
"NOTICE"
|
|
35
|
-
],
|
|
36
|
-
"scripts": {
|
|
37
|
-
"build": "tsc -p tsconfig.json && node scripts/copy-cbx-licence.mjs",
|
|
38
|
-
"check": "tsc -p tsconfig.json --noEmit",
|
|
39
|
-
"test": "tsc -p tsconfig.json && node --test --experimental-strip-types test/*.test.ts",
|
|
40
|
-
"start": "node dist/cli/src/cli.js",
|
|
41
|
-
"prepublishOnly": "npm run sync:plugin && npm run build",
|
|
42
|
-
"test:e2e": "node test/e2e.mjs",
|
|
43
|
-
"test:matrix": "node test/state-matrix.mjs",
|
|
44
|
-
"test:attempt": "node test/attempt-identity.mjs",
|
|
45
|
-
"test:get": "node test/get-safety.mjs",
|
|
46
|
-
"test:live": "node test/e2e.mjs --allow-production && node test/state-matrix.mjs --allow-production && node test/get-safety.mjs --allow-production && node test/get-track.mjs --allow-production && node test/get-protects-edits.mjs --allow-production && node test/upgrade-migration.mjs --allow-production && node test/fault-get.mjs --allow-production && node test/version-floor.mjs --allow-production && node test/fault-submit.mjs --allow-production && node test/race-attempts.mjs --allow-production && node test/attempt-identity.mjs --allow-production",
|
|
47
|
-
"test:getedits": "node test/get-protects-edits.mjs",
|
|
48
|
-
"test:upgrade": "node test/upgrade-migration.mjs",
|
|
49
|
-
"test:faultget": "node test/fault-get.mjs",
|
|
50
|
-
"test:floor": "node test/version-floor.mjs",
|
|
51
|
-
"test:faultsubmit": "node test/fault-submit.mjs",
|
|
52
|
-
"test:race": "node test/race-attempts.mjs",
|
|
53
|
-
"test:runner": "node test/runner-live.mjs",
|
|
54
|
-
"sync:plugin": "node scripts/sync-plugin-version.mjs",
|
|
55
|
-
"test:track": "node test/get-track.mjs"
|
|
56
|
-
},
|
|
57
|
-
"devDependencies": {
|
|
58
|
-
"@types/node": "24.10.1",
|
|
59
|
-
"typescript": "5.9.3"
|
|
60
|
-
},
|
|
61
|
-
"repository": {
|
|
62
|
-
"type": "git",
|
|
63
|
-
"url": "git+https://github.com/AlexC1991/cbx.git",
|
|
64
|
-
"directory": "cli"
|
|
65
|
-
}
|
|
66
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@coderook/cli",
|
|
3
|
+
"version": "0.38.1",
|
|
4
|
+
"description": "The command line for CodeRook (coderook.com): version control and project hosting for projects of any size. Every save is a compressed snapshot, private or public, never used to train AI.",
|
|
5
|
+
"license": "Apache-2.0",
|
|
6
|
+
"homepage": "https://coderook.com",
|
|
7
|
+
"bugs": {
|
|
8
|
+
"url": "https://github.com/AlexC1991/cbx/issues"
|
|
9
|
+
},
|
|
10
|
+
"keywords": [
|
|
11
|
+
"coderook",
|
|
12
|
+
"cbx",
|
|
13
|
+
"version-control",
|
|
14
|
+
"vcs",
|
|
15
|
+
"versioning",
|
|
16
|
+
"project-hosting",
|
|
17
|
+
"backup",
|
|
18
|
+
"snapshots",
|
|
19
|
+
"large-files",
|
|
20
|
+
"gamedev"
|
|
21
|
+
],
|
|
22
|
+
"engines": {
|
|
23
|
+
"node": ">=22.15.0"
|
|
24
|
+
},
|
|
25
|
+
"bin": {
|
|
26
|
+
"cbx": "dist/cli/src/cli.js",
|
|
27
|
+
"coderook": "dist/cli/src/cli.js",
|
|
28
|
+
"git-remote-coderook": "dist/cli/src/git_remote_bin.js"
|
|
29
|
+
},
|
|
30
|
+
"files": [
|
|
31
|
+
".claude-plugin",
|
|
32
|
+
"dist",
|
|
33
|
+
"skills",
|
|
34
|
+
"NOTICE"
|
|
35
|
+
],
|
|
36
|
+
"scripts": {
|
|
37
|
+
"build": "tsc -p tsconfig.json && node scripts/copy-cbx-licence.mjs",
|
|
38
|
+
"check": "tsc -p tsconfig.json --noEmit",
|
|
39
|
+
"test": "tsc -p tsconfig.json && node --test --experimental-strip-types test/*.test.ts",
|
|
40
|
+
"start": "node dist/cli/src/cli.js",
|
|
41
|
+
"prepublishOnly": "npm run sync:plugin && npm run build",
|
|
42
|
+
"test:e2e": "node test/e2e.mjs",
|
|
43
|
+
"test:matrix": "node test/state-matrix.mjs",
|
|
44
|
+
"test:attempt": "node test/attempt-identity.mjs",
|
|
45
|
+
"test:get": "node test/get-safety.mjs",
|
|
46
|
+
"test:live": "node test/e2e.mjs --allow-production && node test/state-matrix.mjs --allow-production && node test/get-safety.mjs --allow-production && node test/get-track.mjs --allow-production && node test/get-protects-edits.mjs --allow-production && node test/upgrade-migration.mjs --allow-production && node test/fault-get.mjs --allow-production && node test/version-floor.mjs --allow-production && node test/fault-submit.mjs --allow-production && node test/race-attempts.mjs --allow-production && node test/attempt-identity.mjs --allow-production",
|
|
47
|
+
"test:getedits": "node test/get-protects-edits.mjs",
|
|
48
|
+
"test:upgrade": "node test/upgrade-migration.mjs",
|
|
49
|
+
"test:faultget": "node test/fault-get.mjs",
|
|
50
|
+
"test:floor": "node test/version-floor.mjs",
|
|
51
|
+
"test:faultsubmit": "node test/fault-submit.mjs",
|
|
52
|
+
"test:race": "node test/race-attempts.mjs",
|
|
53
|
+
"test:runner": "node test/runner-live.mjs",
|
|
54
|
+
"sync:plugin": "node scripts/sync-plugin-version.mjs",
|
|
55
|
+
"test:track": "node test/get-track.mjs"
|
|
56
|
+
},
|
|
57
|
+
"devDependencies": {
|
|
58
|
+
"@types/node": "24.10.1",
|
|
59
|
+
"typescript": "5.9.3"
|
|
60
|
+
},
|
|
61
|
+
"repository": {
|
|
62
|
+
"type": "git",
|
|
63
|
+
"url": "git+https://github.com/AlexC1991/cbx.git",
|
|
64
|
+
"directory": "cli"
|
|
65
|
+
}
|
|
66
|
+
}
|