@coderook/cli 0.41.0 → 0.41.2
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/README.md +16 -3
- package/dist/cbx/src/shared/run_output.js +85 -0
- package/dist/cli/src/cli.js +2 -2
- package/dist/cli/src/mcp.js +14 -0
- package/dist/cli/src/runner.js +63 -24
- package/dist/cli/src/workspace_command.js +4 -1
- package/package.json +3 -2
- package/skills/coderook/SKILL.md +433 -417
|
@@ -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.41.
|
|
5
|
+
"version": "0.41.2",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "ACCA Gaming Productions",
|
|
8
8
|
"url": "https://coderook.com"
|
package/README.md
CHANGED
|
@@ -526,9 +526,22 @@ cbx secret set DEPLOY_TOKEN asked for at a hidden prompt; never shown aga
|
|
|
526
526
|
cbx secret list
|
|
527
527
|
```
|
|
528
528
|
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
529
|
+
Runs get `CODEROOK_API` and `CODEROOK_PROJECT`. A job allowed to act on the
|
|
530
|
+
project also receives a temporary `CODEROOK_TOKEN`; credential-free workspace
|
|
531
|
+
checks and artifact-producing builds receive no project write token.
|
|
532
|
+
|
|
533
|
+
Project secrets require approval tied to the saved source and execution
|
|
534
|
+
settings. PR checks receive none. Workspace release recipes request only their
|
|
535
|
+
configured secret names. Credential-capable jobs cannot retain artifacts: build
|
|
536
|
+
packages in a separate credential-free job, then publish the approved artifact.
|
|
537
|
+
|
|
538
|
+
CLI 0.41.1 buffers complete output lines before masking, including tokens split
|
|
539
|
+
across process chunks. It also isolates the child process's HOME/npm settings
|
|
540
|
+
and stops inheriting unrelated host environment credentials. Supply required
|
|
541
|
+
service credentials through the approved Action, not the runner's own login.
|
|
542
|
+
Use a dedicated native runner: these protections do not sandbox project code
|
|
543
|
+
against the host filesystem or stop a malicious approved command sending a key
|
|
544
|
+
over the network. Review and approve the exact release code before adding keys.
|
|
532
545
|
|
|
533
546
|
## Issues
|
|
534
547
|
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RunOutputStream = void 0;
|
|
4
|
+
exports.secretVariants = secretVariants;
|
|
5
|
+
exports.redactRunOutput = redactRunOutput;
|
|
6
|
+
exports.credentialArtifactPath = credentialArtifactPath;
|
|
7
|
+
exports.artifactContainsSecret = artifactContainsSecret;
|
|
8
|
+
/** Output protection, shared by the API and both runners. Not an exfiltration sandbox. */
|
|
9
|
+
function secretVariants(values) {
|
|
10
|
+
const variants = new Set();
|
|
11
|
+
for (const value of values) {
|
|
12
|
+
if (!value)
|
|
13
|
+
continue;
|
|
14
|
+
for (const part of [value, ...value.split(/\r?\n/)]) {
|
|
15
|
+
if (!part)
|
|
16
|
+
continue;
|
|
17
|
+
variants.add(part);
|
|
18
|
+
variants.add(encodeURIComponent(part));
|
|
19
|
+
variants.add(JSON.stringify(part).slice(1, -1));
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return [...variants].sort((a, b) => b.length - a.length);
|
|
23
|
+
}
|
|
24
|
+
function redactRunOutput(text, values = []) {
|
|
25
|
+
// Strip terminal escapes before matching, so ANSI colour cannot split a value.
|
|
26
|
+
let safe = text.replace(/\x1b\[[0-?]*[ -/]*[@-~]/g, "");
|
|
27
|
+
for (const value of secretVariants(values))
|
|
28
|
+
safe = safe.split(value).join("***");
|
|
29
|
+
// Run tokens must be covered even after revocation, when only their hash remains.
|
|
30
|
+
safe = safe.replace(/\b(?:cr(?:r|w|k|p|pj|pt)_[A-Za-z0-9_-]{16,256}|npm_[A-Za-z0-9]{20,256})\b/g, "***");
|
|
31
|
+
safe = safe.replace(/(\b(?:authorization|proxy-authorization)\s*[:=]\s*)(?:Bearer|Basic)\s+[^\s"']+/gi, "$1***");
|
|
32
|
+
safe = safe.replace(/(\b[a-z][a-z0-9+.-]*:\/\/)[^\s/@]+:[^\s/@]+@/gi, "$1***@");
|
|
33
|
+
safe = safe.replace(/([?&](?:access_token|api_key|apikey|token|password|secret|signature|x-amz-signature)=)[^&#\s"']+/gi, "$1***");
|
|
34
|
+
return safe;
|
|
35
|
+
}
|
|
36
|
+
/** Buffer complete lines: chunk boundaries must never turn a token into two public fragments. */
|
|
37
|
+
class RunOutputStream {
|
|
38
|
+
values;
|
|
39
|
+
limit;
|
|
40
|
+
pending = "";
|
|
41
|
+
omitting = false;
|
|
42
|
+
constructor(values, limit = 64 * 1024) {
|
|
43
|
+
this.values = values;
|
|
44
|
+
this.limit = limit;
|
|
45
|
+
}
|
|
46
|
+
push(chunk) {
|
|
47
|
+
let safe = "";
|
|
48
|
+
for (const part of chunk.split(/([\r\n])/)) {
|
|
49
|
+
if (part === "\r" || part === "\n") {
|
|
50
|
+
if (!this.omitting)
|
|
51
|
+
safe += redactRunOutput(this.pending, this.values) + part;
|
|
52
|
+
this.pending = "";
|
|
53
|
+
this.omitting = false;
|
|
54
|
+
}
|
|
55
|
+
else if (!this.omitting) {
|
|
56
|
+
if (this.pending.length + part.length > this.limit) {
|
|
57
|
+
safe += "[Oversized output line omitted]\n";
|
|
58
|
+
this.pending = "";
|
|
59
|
+
this.omitting = true;
|
|
60
|
+
}
|
|
61
|
+
else
|
|
62
|
+
this.pending += part;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return safe;
|
|
66
|
+
}
|
|
67
|
+
end() {
|
|
68
|
+
const safe = this.omitting ? "" : redactRunOutput(this.pending, this.values);
|
|
69
|
+
this.pending = "";
|
|
70
|
+
this.omitting = false;
|
|
71
|
+
return safe;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
exports.RunOutputStream = RunOutputStream;
|
|
75
|
+
/** Never export local authentication/configuration files, even under a broad glob. */
|
|
76
|
+
function credentialArtifactPath(path) {
|
|
77
|
+
return path.replace(/\\/g, "/").split("/").some(part => /^(?:\.env(?:\..*)?|\.dev\.vars(?:\..*)?|\.npmrc|\.pypirc|\.netrc|\.git-credentials|id_rsa|id_ed25519|credentials(?:\.json)?|token(?:s)?\.json)$/i.test(part) ||
|
|
78
|
+
/\.(?:pem|p12|pfx|key)$/i.test(part));
|
|
79
|
+
}
|
|
80
|
+
/** A best-effort raw-byte check; credential-bearing jobs must not export archives at all. */
|
|
81
|
+
function artifactContainsSecret(bytes, values) {
|
|
82
|
+
const text = new TextDecoder().decode(bytes);
|
|
83
|
+
return secretVariants(values).some(value => text.includes(value)) ||
|
|
84
|
+
/\b(?:cr(?:r|w|k|p|pj|pt)_[A-Za-z0-9_-]{16,256}|npm_[A-Za-z0-9]{20,256})\b/.test(text);
|
|
85
|
+
}
|
package/dist/cli/src/cli.js
CHANGED
|
@@ -2755,9 +2755,9 @@ const SPECS = [
|
|
|
2755
2755
|
name: "workspace",
|
|
2756
2756
|
group: "Workspaces",
|
|
2757
2757
|
summary: "edit, save and run a private project remotely without a checkout",
|
|
2758
|
-
usage: "workspace <open|list|show|read|files|search|diff|patch|save|run|logs|cancel|deploy|preview|handoff|close|recipe|assign|assignments|unassign|checkout> <project> [workspace] [job] | workspace push [folder]",
|
|
2758
|
+
usage: "workspace <usage|open|list|show|read|files|search|diff|patch|save|run|logs|cancel|deploy|preview|handoff|close|recipe|assign|assignments|unassign|checkout> <project> [workspace] [job] | workspace push [folder]",
|
|
2759
2759
|
brief: "workspace <action> <project>",
|
|
2760
|
-
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.",
|
|
2760
|
+
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. Usage reads the shared workspace credits and separate PR allowance without a draft ID. Saving and deploying are separate operations.",
|
|
2761
2761
|
options: [
|
|
2762
2762
|
{ flags: "--data-file <path>", description: "read an operation's JSON from this local file" },
|
|
2763
2763
|
{ flags: "--name <name>", description: "name a new workspace" },
|
package/dist/cli/src/mcp.js
CHANGED
|
@@ -105,6 +105,20 @@ async function withVersion(projectId, wanted) {
|
|
|
105
105
|
return { id: found.id, sequence: found.sequence };
|
|
106
106
|
}
|
|
107
107
|
const TOOLS = [
|
|
108
|
+
{
|
|
109
|
+
name: "coderook_usage",
|
|
110
|
+
description: "Read the project's shared billing-account workspace credits (included, spent, reserved and purchased), separate PR assistant allowance and UTC monthly reset. Supply a project or use the linked folder. Hosted jobs reserve included then purchased credits; insufficient funds leave jobs queued. Native runners use no hosted credits. Workspace top-ups do not refill PR allowance. This tool cannot buy credits or enable recurring payments; purchases stay in CodeRook Subscription or repository Settings.",
|
|
111
|
+
inputSchema: {
|
|
112
|
+
type: "object",
|
|
113
|
+
properties: { project: { type: "string" } },
|
|
114
|
+
additionalProperties: false,
|
|
115
|
+
},
|
|
116
|
+
run: async (input) => {
|
|
117
|
+
const project = await resolve(input.project);
|
|
118
|
+
await assertMayRead(project.id, project.slug);
|
|
119
|
+
return JSON.stringify(await (0, api_js_1.callApi)(`/v1/repositories/${encodeURIComponent(project.id)}/workspace-usage`), null, 2);
|
|
120
|
+
},
|
|
121
|
+
},
|
|
108
122
|
{
|
|
109
123
|
name: "coderook_projects",
|
|
110
124
|
description: "Every project on the signed-in CodeRook account, with how much each " +
|
package/dist/cli/src/runner.js
CHANGED
|
@@ -30,6 +30,8 @@ exports.report = report;
|
|
|
30
30
|
* decision somebody made rather than one they discovered.
|
|
31
31
|
*/
|
|
32
32
|
const node_child_process_1 = require("node:child_process");
|
|
33
|
+
const node_string_decoder_1 = require("node:string_decoder");
|
|
34
|
+
const run_output_js_1 = require("../../cbx/src/shared/run_output.js");
|
|
33
35
|
const node_crypto_1 = require("node:crypto");
|
|
34
36
|
const promises_1 = require("node:fs/promises");
|
|
35
37
|
const node_fs_1 = require("node:fs");
|
|
@@ -44,12 +46,7 @@ const config_js_1 = require("./config.js");
|
|
|
44
46
|
const progress_js_1 = require("./progress.js");
|
|
45
47
|
/** Replace each secret's value with ***, longest first. */
|
|
46
48
|
function maskSecrets(text, secrets) {
|
|
47
|
-
|
|
48
|
-
for (const value of secrets) {
|
|
49
|
-
if (value.length >= 4 && out.includes(value))
|
|
50
|
-
out = out.split(value).join("***");
|
|
51
|
-
}
|
|
52
|
-
return out;
|
|
49
|
+
return (0, run_output_js_1.redactRunOutput)(text, secrets);
|
|
53
50
|
}
|
|
54
51
|
/** How often to tell the service the machine is still working. */
|
|
55
52
|
const HEARTBEAT_MS = 30_000;
|
|
@@ -154,14 +151,15 @@ class LogShipper {
|
|
|
154
151
|
* the run log, which read access is enough to see — so a collaborator could
|
|
155
152
|
* walk off with the account of whoever was hosting the runner.
|
|
156
153
|
*
|
|
157
|
-
*
|
|
158
|
-
*
|
|
159
|
-
*
|
|
154
|
+
* Only platform and toolchain variables are inherited. The command gets a
|
|
155
|
+
* temporary HOME, npm configuration and cache inside the job folder. This
|
|
156
|
+
* prevents accidental inheritance; a native runner is still trusted code on
|
|
157
|
+
* its host, not a filesystem or network sandbox.
|
|
160
158
|
*/
|
|
161
159
|
function withoutCredentials(source) {
|
|
162
160
|
const safe = { ...source };
|
|
163
161
|
for (const name of Object.keys(safe)) {
|
|
164
|
-
if (
|
|
162
|
+
if (!/^(?:PATH|PATHEXT|SYSTEMROOT|WINDIR|COMSPEC|TEMP|TMP|HOME|USERPROFILE|LANG|LC_[A-Z_]+|TERM|COLORTERM|NUMBER_OF_PROCESSORS|PROCESSOR_ARCHITECTURE|JAVA_HOME|DOTNET_ROOT|CARGO_HOME|RUSTUP_HOME|GOROOT|GOPATH)$/i.test(name)) {
|
|
165
163
|
delete safe[name];
|
|
166
164
|
}
|
|
167
165
|
}
|
|
@@ -231,12 +229,20 @@ function defaultLabels() {
|
|
|
231
229
|
* waiting for, and losing a build that passed because the network went odd
|
|
232
230
|
* afterwards would be a worse answer than a pass with a missing download.
|
|
233
231
|
*/
|
|
234
|
-
async function uploadArtifacts(repositoryId, runId, workspace, patterns, say) {
|
|
232
|
+
async function uploadArtifacts(repositoryId, runId, workspace, patterns, say, secrets = []) {
|
|
235
233
|
const wanted = await collectArtifacts(workspace, patterns);
|
|
236
234
|
let kept = 0;
|
|
237
235
|
for (const file of wanted) {
|
|
238
236
|
try {
|
|
237
|
+
if ((0, run_output_js_1.credentialArtifactPath)(file.relative)) {
|
|
238
|
+
say("Skipped an authentication/configuration artifact");
|
|
239
|
+
continue;
|
|
240
|
+
}
|
|
239
241
|
const bytes = await (0, promises_1.readFile)(file.absolute);
|
|
242
|
+
if ((0, run_output_js_1.artifactContainsSecret)(bytes, secrets)) {
|
|
243
|
+
say("Skipped an artifact containing credential material");
|
|
244
|
+
continue;
|
|
245
|
+
}
|
|
240
246
|
const digest = (0, node_crypto_1.createHash)("sha256").update(bytes).digest("hex");
|
|
241
247
|
const token = await (0, config_js_1.loadToken)();
|
|
242
248
|
const stored = await fetch(`${(0, config_js_1.apiOrigin)()}/v1/repositories/${repositoryId}` +
|
|
@@ -374,7 +380,7 @@ async function performRun(repositoryId, run, runner) {
|
|
|
374
380
|
}
|
|
375
381
|
}
|
|
376
382
|
};
|
|
377
|
-
const logs = new LogShipper(repositoryId, run.runId, (line) => progress.say(line));
|
|
383
|
+
const logs = new LogShipper(repositoryId, run.runId, (line) => progress.say(maskSecrets(line, secrets)));
|
|
378
384
|
const started = Date.now();
|
|
379
385
|
const workspace = await (0, promises_1.mkdtemp)(node_path_1.default.join((0, node_os_1.tmpdir)(), "coderook-run-"));
|
|
380
386
|
let beating;
|
|
@@ -452,7 +458,14 @@ async function performRun(repositoryId, run, runner) {
|
|
|
452
458
|
const directory = run.workingDirectory
|
|
453
459
|
? insideWorkspace(workspace, run.workingDirectory)
|
|
454
460
|
: workspace;
|
|
455
|
-
progress.step("Running", command);
|
|
461
|
+
progress.step("Running", maskSecrets(command, secrets));
|
|
462
|
+
// npm and other tools must not automatically reuse the runner owner's login files.
|
|
463
|
+
const home = node_path_1.default.join(workspace, ".run-home");
|
|
464
|
+
await (0, promises_1.mkdir)(home, { recursive: true });
|
|
465
|
+
const temporary = node_path_1.default.join(home, "tmp");
|
|
466
|
+
await (0, promises_1.mkdir)(temporary, { recursive: true });
|
|
467
|
+
const emptyConfig = node_path_1.default.join(home, "empty.npmrc");
|
|
468
|
+
await (0, promises_1.writeFile)(emptyConfig, "", { mode: 0o600 });
|
|
456
469
|
flushing = setInterval(() => void logs.flush(), FLUSH_MS);
|
|
457
470
|
const code = await new Promise((settle) => {
|
|
458
471
|
/*
|
|
@@ -473,6 +486,16 @@ async function performRun(repositoryId, run, runner) {
|
|
|
473
486
|
env: {
|
|
474
487
|
...withoutCredentials(process.env),
|
|
475
488
|
...(run.environment ?? {}),
|
|
489
|
+
HOME: home,
|
|
490
|
+
USERPROFILE: home,
|
|
491
|
+
TEMP: temporary,
|
|
492
|
+
TMP: temporary,
|
|
493
|
+
TMPDIR: temporary,
|
|
494
|
+
XDG_CONFIG_HOME: node_path_1.default.join(home, ".config"),
|
|
495
|
+
XDG_CACHE_HOME: node_path_1.default.join(home, ".cache"),
|
|
496
|
+
NPM_CONFIG_USERCONFIG: emptyConfig,
|
|
497
|
+
NPM_CONFIG_GLOBALCONFIG: emptyConfig,
|
|
498
|
+
NPM_CONFIG_CACHE: node_path_1.default.join(home, ".npm-cache"),
|
|
476
499
|
CI: "true",
|
|
477
500
|
CODEROOK_RUN: String(run.number),
|
|
478
501
|
CODEROOK_VERSION: run.versionSequence
|
|
@@ -485,18 +508,25 @@ async function performRun(repositoryId, run, runner) {
|
|
|
485
508
|
a failed run can be read later; the person watching needs it so they
|
|
486
509
|
can tell a slow build from a stuck one.
|
|
487
510
|
*/
|
|
488
|
-
const
|
|
489
|
-
|
|
511
|
+
const decoders = { stdout: new node_string_decoder_1.StringDecoder("utf8"), stderr: new node_string_decoder_1.StringDecoder("utf8") };
|
|
512
|
+
const protectedStreams = { stdout: new run_output_js_1.RunOutputStream(secrets), stderr: new run_output_js_1.RunOutputStream(secrets) };
|
|
513
|
+
const show = (text, stream) => { if (text) {
|
|
490
514
|
logs.add(text, stream);
|
|
491
515
|
progress.say(text);
|
|
516
|
+
} };
|
|
517
|
+
const tee = (chunk, stream) => show(protectedStreams[stream].push(decoders[stream].write(chunk)), stream);
|
|
518
|
+
const finishOutput = () => {
|
|
519
|
+
for (const stream of ["stdout", "stderr"]) {
|
|
520
|
+
show(protectedStreams[stream].push(decoders[stream].end()) + protectedStreams[stream].end(), stream);
|
|
521
|
+
}
|
|
492
522
|
};
|
|
493
523
|
child.stdout?.on("data", (chunk) => tee(chunk, "stdout"));
|
|
494
524
|
child.stderr?.on("data", (chunk) => tee(chunk, "stderr"));
|
|
495
525
|
child.on("error", (error) => {
|
|
496
|
-
logs.add(errorText(error), "stderr");
|
|
526
|
+
logs.add(maskSecrets(errorText(error), secrets), "stderr");
|
|
497
527
|
settle(1);
|
|
498
528
|
});
|
|
499
|
-
child.on("close", (status) => settle(status ?? 1));
|
|
529
|
+
child.on("close", (status) => { finishOutput(); settle(status ?? 1); });
|
|
500
530
|
if (run.timeoutMinutes) {
|
|
501
531
|
limitTimer = setTimeout(() => {
|
|
502
532
|
timedOut = true;
|
|
@@ -514,16 +544,18 @@ async function performRun(repositoryId, run, runner) {
|
|
|
514
544
|
very log or partial output somebody needs to work out why, and throwing
|
|
515
545
|
that away is exactly when they would have wanted it.
|
|
516
546
|
*/
|
|
517
|
-
if (run.artifactPaths?.length) {
|
|
547
|
+
if (run.artifactPaths?.length && (run.artifactsAllowed ?? !run.secretNames?.length)) {
|
|
518
548
|
progress.step("Keeping what it built");
|
|
519
549
|
const kept = await uploadArtifacts(repositoryId, run.runId, workspace, run.artifactPaths, (line) => {
|
|
520
|
-
logs.add(line, "stdout");
|
|
521
|
-
progress.say(line);
|
|
522
|
-
});
|
|
550
|
+
logs.add(maskSecrets(line, secrets), "stdout");
|
|
551
|
+
progress.say(maskSecrets(line, secrets));
|
|
552
|
+
}, secrets);
|
|
523
553
|
if (!kept) {
|
|
524
554
|
logs.add("Nothing matched the paths this workflow keeps.", "stdout");
|
|
525
555
|
}
|
|
526
556
|
}
|
|
557
|
+
if (run.artifactPaths?.length && !(run.artifactsAllowed ?? !run.secretNames?.length))
|
|
558
|
+
logs.add("Artifacts disabled for this credential-capable run; use a separate credential-free build job.", "stdout");
|
|
527
559
|
await logs.flush();
|
|
528
560
|
const seconds = Math.round((Date.now() - started) / 1000);
|
|
529
561
|
if (timedOut)
|
|
@@ -535,9 +567,10 @@ async function performRun(repositoryId, run, runner) {
|
|
|
535
567
|
: { status: "failed", summary: `Exited ${code} after ${seconds}s` };
|
|
536
568
|
}
|
|
537
569
|
catch (error) {
|
|
538
|
-
|
|
570
|
+
const safeError = maskSecrets(errorText(error), secrets);
|
|
571
|
+
logs.add(safeError, "stderr");
|
|
539
572
|
await logs.flush();
|
|
540
|
-
return { status: "failed", summary:
|
|
573
|
+
return { status: "failed", summary: safeError.slice(0, 200) };
|
|
541
574
|
}
|
|
542
575
|
finally {
|
|
543
576
|
/*
|
|
@@ -552,7 +585,13 @@ async function performRun(repositoryId, run, runner) {
|
|
|
552
585
|
if (flushing)
|
|
553
586
|
clearInterval(flushing);
|
|
554
587
|
await logs.flush();
|
|
555
|
-
await (0, promises_1.rm)(workspace, { recursive: true, force: true }).catch(() => {
|
|
588
|
+
await (0, promises_1.rm)(workspace, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 }).catch(async () => {
|
|
589
|
+
const summary = "Runner cleanup failed. Stop this runner and remove its temporary job folder before processing more credential-bearing work.";
|
|
590
|
+
// Report a final failure before stopping the runner, rather than leaving
|
|
591
|
+
// a completed publish queued for a lease-expiry retry.
|
|
592
|
+
await report(repositoryId, run.runId, { status: "failed", summary }, Date.now() - started).catch(() => undefined);
|
|
593
|
+
throw new Error(summary);
|
|
594
|
+
});
|
|
556
595
|
}
|
|
557
596
|
}
|
|
558
597
|
/** Say how it went. */
|
|
@@ -41,7 +41,10 @@ async function commandWorkspace(parsed) {
|
|
|
41
41
|
const key = flag("key") ?? data.operationKey;
|
|
42
42
|
const revision = integer("revision") ?? data.expectedRevision;
|
|
43
43
|
let path = `${root}/workspaces`, method = "GET", body;
|
|
44
|
-
if (operation === "
|
|
44
|
+
if (operation === "usage") {
|
|
45
|
+
path = `${root}/workspace-usage`;
|
|
46
|
+
}
|
|
47
|
+
else if (operation === "open") {
|
|
45
48
|
method = "POST";
|
|
46
49
|
body = {
|
|
47
50
|
...data,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@coderook/cli",
|
|
3
|
-
"version": "0.41.
|
|
3
|
+
"version": "0.41.2",
|
|
4
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
5
|
"license": "Apache-2.0",
|
|
6
6
|
"homepage": "https://coderook.com",
|
|
@@ -53,7 +53,8 @@
|
|
|
53
53
|
"test:runner": "node test/runner-live.mjs",
|
|
54
54
|
"sync:plugin": "node scripts/sync-plugin-version.mjs",
|
|
55
55
|
"test:track": "node test/get-track.mjs",
|
|
56
|
-
"package:remote": "node ../scripts/package-cli-release.mjs"
|
|
56
|
+
"package:remote": "node ../scripts/package-cli-release.mjs",
|
|
57
|
+
"publish:remote": "node ../scripts/publish-cli-release.mjs"
|
|
57
58
|
},
|
|
58
59
|
"devDependencies": {
|
|
59
60
|
"@types/node": "24.10.1",
|