@coderook/cli 0.22.2 → 0.24.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.
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __importDefault = (this && this.__importDefault) || function (mod) {
4
+ return (mod && mod.__esModule) ? mod : { "default": mod };
5
+ };
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ /*
8
+ The executable half of `git-remote-coderook`.
9
+
10
+ Split from the module that does the work so that module can be imported by a
11
+ test without starting the protocol loop. Merging the two costs nothing to
12
+ write and means every test of a pure function first has to stop a process
13
+ from reading stdin and answering git.
14
+ */
15
+ const node_process_1 = __importDefault(require("node:process"));
16
+ const git_remote_js_1 = require("./git_remote.js");
17
+ (0, git_remote_js_1.main)(node_process_1.default.argv.slice(2))
18
+ .then((code) => node_process_1.default.exit(code))
19
+ .catch((error) => {
20
+ node_process_1.default.stderr.write(`git-remote-coderook: ${error instanceof Error ? error.message : String(error)}\n`);
21
+ node_process_1.default.exit(1);
22
+ });
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  /**
3
- * What `coderook help` prints.
3
+ * What `cbx help` prints.
4
4
  *
5
5
  * Generated from the registry rather than written out, so a command that
6
6
  * exists is listed and a command that is listed exists. The previous usage
@@ -30,7 +30,7 @@ function renderHelp(registry, version) {
30
30
  const listed = registry.specs.filter((spec) => !spec.deprecatedBy);
31
31
  const column = Math.min(nameColumn(listed), 34);
32
32
  const lines = [
33
- `${bold("coderook")} ${dim(version)} — CodeRook from the command line`,
33
+ `${bold("cbx")} ${dim(version)} — the CodeBox engine, for CodeRook`,
34
34
  "",
35
35
  ];
36
36
  for (const group of registry_js_1.GROUP_ORDER) {
@@ -43,18 +43,18 @@ function renderHelp(registry, version) {
43
43
  }
44
44
  lines.push("");
45
45
  }
46
- lines.push(dim("coderook help <command> what one command does, in full"), dim("CODEROOK_TOKEN is used when set, so automated runs need nothing on disk."), dim("CODEROOK_API_URL points at another service."));
46
+ lines.push(dim("cbx help <command> what one command does, in full"), dim("CODEROOK_TOKEN is used when set, so automated runs need nothing on disk."), dim("CODEROOK_API_URL points at another service."));
47
47
  return lines.join("\n");
48
48
  }
49
49
  function renderCommandHelp(spec) {
50
50
  const lines = [
51
- `${bold("coderook " + spec.name)} — ${spec.summary}`,
51
+ `${bold("cbx " + spec.name)} — ${spec.summary}`,
52
52
  "",
53
53
  bold("Usage"),
54
- ` coderook ${spec.usage}`,
54
+ ` cbx ${spec.usage}`,
55
55
  ];
56
56
  if (spec.aliases?.length) {
57
- lines.push("", bold("Also"), ` ${spec.aliases.map((alias) => `coderook ${alias}`).join(" ")}`);
57
+ lines.push("", bold("Also"), ` ${spec.aliases.map((alias) => `cbx ${alias}`).join(" ")}`);
58
58
  }
59
59
  if (spec.detail) {
60
60
  lines.push("", spec.detail.trim());
@@ -82,7 +82,7 @@ function renderCommandHelp(spec) {
82
82
  function renderUnknown(typed, suggestion) {
83
83
  const lines = [`Unknown command: ${typed}`];
84
84
  if (suggestion)
85
- lines.push(`Did you mean ${accent("coderook " + suggestion)}?`);
86
- lines.push(dim("Run coderook help to see everything."));
85
+ lines.push(`Did you mean ${accent("cbx " + suggestion)}?`);
86
+ lines.push(dim("Run cbx help to see everything."));
87
87
  return lines.join("\n");
88
88
  }
@@ -0,0 +1,150 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.projectNameFromUrl = projectNameFromUrl;
7
+ exports.looksLikeRepositoryUrl = looksLikeRepositoryUrl;
8
+ exports.gitAvailable = gitAvailable;
9
+ exports.fetchSnapshot = fetchSnapshot;
10
+ exports.measure = measure;
11
+ exports.humanBytes = humanBytes;
12
+ const node_child_process_1 = require("node:child_process");
13
+ const promises_1 = require("node:fs/promises");
14
+ const node_os_1 = require("node:os");
15
+ const node_path_1 = __importDefault(require("node:path"));
16
+ const node_util_1 = require("node:util");
17
+ const run = (0, node_util_1.promisify)(node_child_process_1.execFile);
18
+ /**
19
+ * The repository's own name, from the last path segment.
20
+ *
21
+ * Deliberately not the host: an address with no path at all is not a
22
+ * repository, and naming somebody's project `github.com` because the URL was
23
+ * incomplete is the sort of thing they would only notice later.
24
+ */
25
+ function projectNameFromUrl(url) {
26
+ const trimmed = url.trim().replace(/\/+$/, "");
27
+ /* Drop the scheme and authority so only path segments remain. */
28
+ const withoutScheme = trimmed.replace(/^[a-z][a-z0-9+.-]*:\/\//i, "");
29
+ const afterHost = /^[^/]*:/.test(withoutScheme)
30
+ ? /* scp-style git@host:owner/name */
31
+ withoutScheme.slice(withoutScheme.indexOf(":") + 1)
32
+ : withoutScheme.slice(withoutScheme.indexOf("/") + 1);
33
+ const hasPath = withoutScheme.includes("/") || /^[^/]*:/.test(withoutScheme);
34
+ if (!hasPath)
35
+ return "imported-project";
36
+ const tail = afterHost.split("/").filter(Boolean).pop() ?? "";
37
+ const name = tail.replace(/\.git$/i, "").trim();
38
+ return name || "imported-project";
39
+ }
40
+ /**
41
+ * Refuse anything that is not a repository location.
42
+ *
43
+ * `git clone` will happily treat a local path as a source, and a URL typed
44
+ * with a scheme this does not expect is more likely a mistake than an
45
+ * intention. Being narrow here keeps the command from doing something
46
+ * surprising with an argument that was meant for something else.
47
+ */
48
+ function looksLikeRepositoryUrl(url) {
49
+ const value = url.trim();
50
+ if (/^(https?|git|ssh):\/\//i.test(value))
51
+ return true;
52
+ /* scp-style: git@host:owner/name.git */
53
+ if (/^[A-Za-z0-9._-]+@[A-Za-z0-9.-]+:[^\s]+$/.test(value))
54
+ return true;
55
+ return false;
56
+ }
57
+ async function exists(target) {
58
+ try {
59
+ await (0, promises_1.access)(target);
60
+ return true;
61
+ }
62
+ catch {
63
+ return false;
64
+ }
65
+ }
66
+ async function isEmptyDirectory(target) {
67
+ try {
68
+ const entries = await (0, promises_1.readdir)(target);
69
+ return entries.length === 0;
70
+ }
71
+ catch {
72
+ return true;
73
+ }
74
+ }
75
+ async function gitAvailable() {
76
+ try {
77
+ await run("git", ["--version"], { windowsHide: true });
78
+ return true;
79
+ }
80
+ catch {
81
+ return false;
82
+ }
83
+ }
84
+ /**
85
+ * Shallow-clone into a working folder and strip the Git metadata.
86
+ *
87
+ * Returns where the files landed. The caller publishes from there with the
88
+ * ordinary save path, so an import produces exactly the Version an ordinary
89
+ * save of the same files would.
90
+ */
91
+ async function fetchSnapshot(url, into, log = () => { }) {
92
+ const name = projectNameFromUrl(url);
93
+ let folder;
94
+ let temporary = false;
95
+ if (into) {
96
+ folder = node_path_1.default.resolve(into);
97
+ if ((await exists(folder)) && !(await isEmptyDirectory(folder))) {
98
+ throw new Error(`${folder} already has files in it. Import needs an empty folder, ` +
99
+ `so that nothing here is overwritten by what arrives.`);
100
+ }
101
+ }
102
+ else {
103
+ const base = await (0, promises_1.mkdtemp)(node_path_1.default.join((0, node_os_1.tmpdir)(), "coderook-import-"));
104
+ folder = node_path_1.default.join(base, name);
105
+ temporary = true;
106
+ }
107
+ log(`Fetching ${url}`);
108
+ /*
109
+ --depth 1 for the reason in the header. --single-branch keeps it to the
110
+ default branch: an import takes a snapshot of one line of work, and
111
+ fetching every branch's tip would cost time to produce content this
112
+ command then discards.
113
+ */
114
+ await run("git", ["clone", "--depth", "1", "--single-branch", url, folder], { windowsHide: true, maxBuffer: 32 * 1024 * 1024 });
115
+ const gitDirectory = node_path_1.default.join(folder, ".git");
116
+ if (await exists(gitDirectory)) {
117
+ await (0, promises_1.rm)(gitDirectory, { recursive: true, force: true });
118
+ }
119
+ return { folder, name, temporary };
120
+ }
121
+ /** Total bytes and file count of the fetched tree, for the summary line. */
122
+ async function measure(folder) {
123
+ let files = 0;
124
+ let bytes = 0;
125
+ const walk = async (directory) => {
126
+ for (const entry of await (0, promises_1.readdir)(directory, { withFileTypes: true })) {
127
+ const full = node_path_1.default.join(directory, entry.name);
128
+ if (entry.isDirectory()) {
129
+ await walk(full);
130
+ continue;
131
+ }
132
+ if (!entry.isFile())
133
+ continue;
134
+ files += 1;
135
+ bytes += (await (0, promises_1.stat)(full)).size;
136
+ }
137
+ };
138
+ await walk(folder);
139
+ return { files, bytes };
140
+ }
141
+ function humanBytes(value) {
142
+ const units = ["B", "KB", "MB", "GB", "TB"];
143
+ let size = value;
144
+ let unit = 0;
145
+ while (size >= 1024 && unit < units.length - 1) {
146
+ size /= 1024;
147
+ unit += 1;
148
+ }
149
+ return `${unit === 0 ? size : size.toFixed(size < 10 ? 2 : 1)} ${units[unit]}`;
150
+ }
@@ -73,7 +73,7 @@ async function commandLicence(parsed) {
73
73
  console.log(` ${dim(licence.summary)}`);
74
74
  }
75
75
  console.log("");
76
- console.log(`Add one with ${accent("coderook licence MIT")}. ` +
76
+ console.log(`Add one with ${accent("cbx licence MIT")}. ` +
77
77
  dim(`Nothing is written until you name one.`));
78
78
  return 0;
79
79
  }
@@ -98,7 +98,7 @@ async function commandLicence(parsed) {
98
98
  const licence = (0, licences_js_1.licenceById)(wanted);
99
99
  if (!licence) {
100
100
  console.error(red(`No licence called ${wanted}.`));
101
- console.error(`Run ${accent("coderook licence")} to see the list.`);
101
+ console.error(`Run ${accent("cbx licence")} to see the list.`);
102
102
  return 1;
103
103
  }
104
104
  /*
@@ -162,14 +162,14 @@ async function commandIgnoreTemplate(parsed) {
162
162
  console.log(" " + names.slice(at, at + perRow).map((name) => name.padEnd(width)).join(""));
163
163
  }
164
164
  console.log("");
165
- console.log(`Add one with ${accent("coderook ignore --template Python")}. ` +
165
+ console.log(`Add one with ${accent("cbx ignore --template Python")}. ` +
166
166
  dim("Several is normal — a language, an editor, an operating system."));
167
167
  return 0;
168
168
  }
169
169
  const template = ignore_templates_js_1.IGNORE_TEMPLATES[wanted.toLowerCase()];
170
170
  if (!template) {
171
171
  console.error(red(`No rules called ${wanted}.`));
172
- console.error(`Run ${accent("coderook ignore --template list")} to see what there is.`);
172
+ console.error(`Run ${accent("cbx ignore --template list")} to see what there is.`);
173
173
  return 1;
174
174
  }
175
175
  const rules = await (0, worktree_js_1.readRules)(folder);
@@ -185,7 +185,7 @@ async function commandIgnoreTemplate(parsed) {
185
185
  console.log(green(`Added ${template.name} — ${lines.length} rules.`));
186
186
  console.log(dim(` ${node_path_1.default.join(folder, ".gitignore")}`));
187
187
  console.log("");
188
- console.log(dim(`See what it leaves behind with `) + accent("coderook status"));
188
+ console.log(dim(`See what it leaves behind with `) + accent("cbx status"));
189
189
  return 0;
190
190
  }
191
191
  function flagValue(parsed, name) {
@@ -196,7 +196,7 @@ const TOOLS = [
196
196
  throw new Error(`No file "${wanted}" in v${version.sequence}.`);
197
197
  if (file.sourceSize > READ_LIMIT) {
198
198
  return (`${wanted} is ${bytes(file.sourceSize)}, larger than this tool will ` +
199
- `paste into a conversation. Fetch the project with \`coderook get\` ` +
199
+ `paste into a conversation. Fetch the project with \`cbx get\` ` +
200
200
  `and read it from disk.`);
201
201
  }
202
202
  /*
@@ -0,0 +1,37 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.uploadRequestFor = uploadRequestFor;
4
+ exports.classifyPublishFailure = classifyPublishFailure;
5
+ /** Turn what a caller has into what the uploader expects. */
6
+ function uploadRequestFor(input) {
7
+ return {
8
+ localPath: input.localPath,
9
+ // Both lists. See the note at the top of this file.
10
+ include: [...input.changed, ...input.deleted],
11
+ deletions: input.deleted,
12
+ message: input.message,
13
+ projectName: input.projectName,
14
+ repositoryId: input.repositoryId,
15
+ baseVersionId: input.baseVersionId,
16
+ ...(input.track ? { track: input.track } : {}),
17
+ ...(input.allowIgnored ? { allowIgnored: true } : {}),
18
+ ...(input.acknowledged ? { acknowledged: true } : {}),
19
+ ...(input.acknowledgedNoLicence ? { acknowledgedNoLicence: true } : {}),
20
+ ...(input.baseVersionId
21
+ ? { expectedHeadVersionId: input.baseVersionId }
22
+ : {}),
23
+ ...(input.known ? { known: input.known } : {}),
24
+ };
25
+ }
26
+ function classifyPublishFailure(error) {
27
+ const code = error?.code;
28
+ const message = error instanceof Error ? error.message : String(error);
29
+ if (code === "merge_required" || code === "track_moved") {
30
+ return { kind: "conflict", message };
31
+ }
32
+ if (!code &&
33
+ /fetch failed|ECONNRESET|socket hang up|network|ETIMEDOUT/i.test(message)) {
34
+ return { kind: "interrupted", message };
35
+ }
36
+ return null;
37
+ }
@@ -7,7 +7,7 @@
7
7
  * command gets added and the text does not mention it, or an option is renamed
8
8
  * and the text still lists the old one. Nobody notices, because nothing checks.
9
9
  *
10
- * Here the description is part of the command. `coderook help` is generated
10
+ * Here the description is part of the command. `cbx help` is generated
11
11
  * from the same rows that decide what runs, so the two cannot disagree — and a
12
12
  * command added without a summary is a type error rather than an omission.
13
13
  */
@@ -45,7 +45,7 @@ const FLUSH_MS = 2_000;
45
45
  async function call(route, options = {}) {
46
46
  const token = await (0, config_js_1.loadToken)();
47
47
  if (!token)
48
- throw new Error("Not signed in. Run: coderook sign-in");
48
+ throw new Error("Not signed in. Run: cbx sign-in");
49
49
  const response = await fetch(`${(0, config_js_1.apiOrigin)()}${route}`, {
50
50
  method: options.method ?? "GET",
51
51
  headers: {
@@ -127,7 +127,7 @@ class LogShipper {
127
127
  *
128
128
  * Running somebody's command on your machine is the deal a runner makes, and
129
129
  * it is stated at every startup. Handing them the token that machine signs in
130
- * with is not: `coderook runner` reads CODEROOK_TOKEN from the environment so
130
+ * with is not: `cbx runner` reads CODEROOK_TOKEN from the environment so
131
131
  * automated runs can supply it, and every child process inherited it. A
132
132
  * workflow set to `env` by anyone with write access printed it straight into
133
133
  * the run log, which read access is enough to see — so a collaborator could
@@ -12,6 +12,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
12
12
  };
13
13
  Object.defineProperty(exports, "__esModule", { value: true });
14
14
  exports.commandIssues = commandIssues;
15
+ exports.commandRelease = commandRelease;
15
16
  exports.commandReleases = commandReleases;
16
17
  exports.commandCollaborators = commandCollaborators;
17
18
  exports.commandWatch = commandWatch;
@@ -72,6 +73,55 @@ async function commandIssues(parsed) {
72
73
  return 0;
73
74
  }
74
75
  /** Published releases, newest first. */
76
+ /**
77
+ * Name a version, which is what a release is.
78
+ *
79
+ * The service has had `markRelease` since releases existed, and nothing on
80
+ * this side could reach it — a project could be given a release by the
81
+ * website and by nothing else. That gap is why `git push --tags` needs this:
82
+ * a git tag is a name for a version, so it has to call the same thing a person
83
+ * calls, not a path of its own. A capability git can reach and the CLI cannot
84
+ * would be a feature that exists twice and agrees by luck.
85
+ */
86
+ async function commandRelease(parsed) {
87
+ const name = parsed.positional[0];
88
+ if (!name) {
89
+ console.error(red("Name the release: cbx release v1.0"));
90
+ return 1;
91
+ }
92
+ const project = await (0, project_commands_js_1.resolveProject)(parsed.positional[1]);
93
+ if (!project)
94
+ return 1;
95
+ const all = await (0, api_js_1.versions)(project.id);
96
+ if (!all.length) {
97
+ console.error(red("This project has no versions to release."));
98
+ return 1;
99
+ }
100
+ /*
101
+ The newest version unless one is named. A release usually means "what I
102
+ have just finished", and asking for the number every time would make the
103
+ common case the awkward one.
104
+ */
105
+ const wanted = parsed.flags.get("version");
106
+ const target = typeof wanted === "string"
107
+ ? all.find((version) => String(version.sequence) === wanted.replace(/^v/i, ""))
108
+ : all[0];
109
+ if (!target) {
110
+ console.error(red(`This project has no version ${String(wanted)}.`));
111
+ return 1;
112
+ }
113
+ const notes = parsed.flags.get("notes");
114
+ try {
115
+ await (0, api_js_1.markRelease)(project.id, target.id, name, typeof notes === "string" ? notes : null);
116
+ }
117
+ catch (error) {
118
+ console.error(red(error instanceof Error ? error.message : String(error)));
119
+ return 1;
120
+ }
121
+ console.log(`${green("Released")} ${bold(name)} ${dim("at")} v${target.sequence}` +
122
+ `${dim(" — ")}${target.message.split("\n")[0].slice(0, 48)}`);
123
+ return 0;
124
+ }
75
125
  async function commandReleases(parsed) {
76
126
  const project = await (0, project_commands_js_1.resolveProject)(parsed.positional[0]);
77
127
  if (!project)
@@ -243,7 +293,7 @@ async function commandRuns(parsed) {
243
293
  console.log(` ${dim(aside.join(" · "))}`);
244
294
  }
245
295
  console.log("");
246
- console.log(dim(" Read one with: coderook logs <number>"));
296
+ console.log(dim(" Read one with: cbx logs <number>"));
247
297
  return 0;
248
298
  }
249
299
  /**
@@ -255,7 +305,7 @@ async function commandRuns(parsed) {
255
305
  async function commandLogs(parsed) {
256
306
  const asked = parsed.positional[0];
257
307
  if (!asked) {
258
- console.error(red("Which run? Try: coderook logs 12"));
308
+ console.error(red("Which run? Try: cbx logs 12"));
259
309
  return 1;
260
310
  }
261
311
  const number = Number(asked.replace(/^#/, ""));
@@ -314,7 +364,7 @@ async function commandTokens(parsed) {
314
364
  console.log(` ${token.name.padEnd(26)} ${used.padEnd(16)} ${dim(token.id)}`);
315
365
  }
316
366
  console.log("");
317
- console.log(dim(" Revoke one with: coderook tokens --revoke <id>"));
367
+ console.log(dim(" Revoke one with: cbx tokens --revoke <id>"));
318
368
  return 0;
319
369
  }
320
370
  /**
@@ -9,7 +9,7 @@
9
9
  *
10
10
  * A skill is a file in a folder. The command line is already installed and
11
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
12
+ * clone, no second account. `cbx skill` and the assistant knows what
13
13
  * CodeRook is and how to drive it from then on, in every project.
14
14
  *
15
15
  * The skill teaches it the command line rather than the MCP server. Both work,
@@ -106,8 +106,8 @@ async function commandSkill(parsed) {
106
106
  console.log(dim("or run it by name — ") + accent("/coderook"));
107
107
  console.log("");
108
108
  console.log(dim("Signed in? ") +
109
- bold("coderook whoami") +
109
+ bold("cbx whoami") +
110
110
  dim(" · if not: ") +
111
- bold("coderook sign-in"));
111
+ bold("cbx sign-in"));
112
112
  return 0;
113
113
  }
@@ -88,7 +88,7 @@ async function commandTracks(parsed) {
88
88
  console.log(bold("Waiting on a decision"));
89
89
  for (const track of merges)
90
90
  console.log(` ${describe(track, current)}`);
91
- console.log(dim(" Finish one with ") + "coderook merge");
91
+ console.log(dim(" Finish one with ") + "cbx merge");
92
92
  }
93
93
  console.log("");
94
94
  console.log(dim("This folder saves to ") + accent(current));
@@ -100,20 +100,20 @@ async function commandTracks(parsed) {
100
100
  * Switching is a statement about where the next save goes and nothing else:
101
101
  * no files move and nothing is fetched, which is why it is instant and why
102
102
  * it is safe to change your mind. Getting the other line's files is
103
- * `coderook get`, deliberately a separate act.
103
+ * `cbx get`, deliberately a separate act.
104
104
  */
105
105
  async function commandTrack(parsed) {
106
106
  const folder = node_process_1.default.cwd();
107
107
  const link = await (0, config_js_1.readLink)(folder);
108
108
  if (!link) {
109
- console.error(red("This folder is not linked to a project. Run ") + "coderook get" + red(" first."));
109
+ console.error(red("This folder is not linked to a project. Run ") + "cbx get" + red(" first."));
110
110
  return 1;
111
111
  }
112
112
  const wanted = parsed.positional[0];
113
113
  const current = link.track?.trim() || exports.DEFAULT_TRACK;
114
114
  if (!wanted) {
115
115
  console.log(accent(current));
116
- console.log(dim("Switch with ") + "coderook track <name>");
116
+ console.log(dim("Switch with ") + "cbx track <name>");
117
117
  return 0;
118
118
  }
119
119
  const starting = parsed.flags.has("new") || parsed.flags.has("n");
@@ -130,12 +130,12 @@ async function commandTrack(parsed) {
130
130
  work somewhere nobody will look for it.
131
131
  */
132
132
  console.error(red(`No line called "${wanted}" on this project.`));
133
- console.error(dim("Start one with ") + `coderook track ${wanted} --new`);
133
+ console.error(dim("Start one with ") + `cbx track ${wanted} --new`);
134
134
  return 1;
135
135
  }
136
136
  if (existing?.kind === "merge") {
137
137
  console.error(red(`"${wanted}" is a merge waiting on a decision, not a line to save onto.`));
138
- console.error(dim("Finish it with ") + "coderook merge");
138
+ console.error(dim("Finish it with ") + "cbx merge");
139
139
  return 1;
140
140
  }
141
141
  if (starting) {
@@ -151,7 +151,7 @@ async function commandTrack(parsed) {
151
151
  await (0, config_js_1.writeLink)(folder, { ...link, track: wanted });
152
152
  console.log(dim("This folder now saves to ") + accent(wanted));
153
153
  if (existing) {
154
- console.log(dim("Its files are not here yet — fetch them with ") + `coderook get`);
154
+ console.log(dim("Its files are not here yet — fetch them with ") + `cbx get`);
155
155
  }
156
156
  return 0;
157
157
  }
@@ -77,8 +77,18 @@ class Tracks {
77
77
  * accept, so this passes the name through rather than pre-judging it —
78
78
  * a rule enforced in two places is a rule that will disagree with itself.
79
79
  */
80
- async create(repositoryId, name) {
81
- const body = await this.call(`/v1/repositories/${repositoryId}/tracks`, { method: "POST", body: JSON.stringify({ name }) });
80
+ async create(repositoryId, name,
81
+ /*
82
+ Where the line starts, when the caller knows better than "wherever the
83
+ project is now". Somebody clicking New line means from here; a git branch
84
+ means from the version matching the commit it forked at, which is
85
+ usually not the head. Omitted keeps the original behaviour exactly.
86
+ */
87
+ fromVersionId) {
88
+ const body = await this.call(`/v1/repositories/${repositoryId}/tracks`, {
89
+ method: "POST",
90
+ body: JSON.stringify(fromVersionId ? { name, fromVersionId } : { name }),
91
+ });
82
92
  return body.track;
83
93
  }
84
94
  /** Merges that have not been applied or abandoned. */
@@ -327,7 +327,16 @@ class Uploader {
327
327
  // A version is a snapshot, not a delta, so it has to name every file in
328
328
  // the project — not merely the ones being sent this time. Anything
329
329
  // unchanged keeps the object the previous version already pointed at.
330
- const rules = await (0, worktree_js_1.readRules)(request.localPath);
330
+ /*
331
+ An import filters nothing. The rules keep local mess out of a working
332
+ folder, but an imported tree is exactly what the source repository
333
+ tracked — there is no mess in it, and anything the source tracked in
334
+ spite of its own rules (which git does, for files committed before the
335
+ rule) would otherwise be dropped without being mentioned.
336
+ */
337
+ const rules = request.allowIgnored
338
+ ? { shared: "", local: "" }
339
+ : await (0, worktree_js_1.readRules)(request.localPath);
331
340
  /*
332
341
  Surveyed, not listed.
333
342
 
@@ -1152,6 +1161,7 @@ class Uploader {
1152
1161
  ? {}
1153
1162
  : { expectedHeadVersionId: request.expectedHeadVersionId }),
1154
1163
  ...(request.track ? { track: request.track } : {}),
1164
+ ...(request.allowIgnored ? { allowIgnored: true } : {}),
1155
1165
  /*
1156
1166
  Names this attempt so a retry after a lost connection is answered
1157
1167
  with the version already made, rather than making a second one.