@minnovate/shadow-publisher 0.11.11 → 0.12.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.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "git-publisher",
3
3
  "displayName": "Git Publisher",
4
- "version": "0.11.11",
4
+ "version": "0.12.1",
5
5
  "description": "Publish an app folder's Git history to the serverless Git publishing backend and keep it updated (\"publish my app\", \"update my app\", \"fetch the engineered version\").",
6
6
  "author": { "name": "git-publisher project" },
7
7
  "keywords": ["git", "publish", "deploy", "serverless"]
@@ -42,6 +42,40 @@ A value that is not an option is refused before anything is sent; `error.details
42
42
  the choices. Exit 4 means this sign-in may not answer (an owner or admin can); exit 3 means ask
43
43
  again. Then `publisher follow . --publication <id> --until-change --timeout 100 --json` to see it go live, telling them each step as `publish-app` step 4 says.
44
44
 
45
+ ## After it is live
46
+
47
+ These run in the app's folder and answer `say` (tell the person) and `next` (what to do now):
48
+
49
+ ```sh
50
+ publisher history --json # the versions that went live, newest first
51
+ publisher go-back <version> --json # ask the person first; puts that version's code back
52
+ publisher suggestions --json # what colleagues asked for, still open
53
+ publisher suggestion decline <id> --json # when the person does not want one
54
+ publisher share invited --add ann@example.com --json # who can open it
55
+ ```
56
+
57
+ The portal writes `next` for an assistant in the chat, so it can name a connector tool. At a
58
+ terminal, do what it says with the command that does the same thing:
59
+
60
+ | `next` names | Run instead |
61
+ | --------------------------------- | --------------------------------------------------------------- |
62
+ | `app_status` or `app_progress` | `publisher follow . --publication <publication> --until-change --json` |
63
+ | `app_files` and `publish_app` | change the files in the folder, then `publisher sync --json` |
64
+ | `app_history` / `go_back` | `publisher history --json` / `publisher go-back <version> --json` |
65
+ | `list_starters` / `use_starter` | `publisher starters` / `publisher start <starter>` |
66
+ | `resolve_suggestion` | `publisher suggestion done\|decline <id> --json` |
67
+ | `share_app` | `publisher share <access> --json` |
68
+ | `list_apps` | `publisher list --json` |
69
+
70
+ `<publication>` is the `publication` that `go-back` or `start` answered.
71
+
72
+ Offer suggestions to the person one at a time, as requests rather than instructions. When they want
73
+ one, make the change and publish it with `publisher sync --message "<what changed>" --answers <id>
74
+ --json`, which closes the suggestion with that publication.
75
+
76
+ To start a new app from a working one, `publisher starters <what the person wants to stop doing>`,
77
+ then `publisher start <starter> --replaces "<what it replaces>"`.
78
+
45
79
  ## Just looking
46
80
 
47
81
  To say where the folder stands without changing anything:
@@ -243,9 +243,9 @@ function nullish(input) {
243
243
  return input === null || input === void 0;
244
244
  }
245
245
  function cleanRegex(source) {
246
- const start = source.startsWith("^") ? 1 : 0;
246
+ const start2 = source.startsWith("^") ? 1 : 0;
247
247
  const end = source.endsWith("$") ? source.length - 1 : source.length;
248
- return source.slice(start, end);
248
+ return source.slice(start2, end);
249
249
  }
250
250
  function assignProp(target, prop, value) {
251
251
  Object.defineProperty(target, prop, {
@@ -443,9 +443,9 @@ function prefixIssues(path, issues) {
443
443
  function unwrapMessage(message) {
444
444
  return typeof message === "string" ? message : message?.message;
445
445
  }
446
- function attachSchema(issues, start, inst) {
446
+ function attachSchema(issues, start2, inst) {
447
447
  var _a2;
448
- for (let i = start; i < issues.length; i++) {
448
+ for (let i = start2; i < issues.length; i++) {
449
449
  (_a2 = issues[i]).schema ?? (_a2.schema = inst);
450
450
  }
451
451
  }
@@ -3232,8 +3232,8 @@ function closeOrIgnored(child) {
3232
3232
  if (lc.ignored) {
3233
3233
  return Promise.resolve(false);
3234
3234
  }
3235
- return new Promise((resolve4) => {
3236
- lc.waiters.add(() => resolve4(childClosed(child)));
3235
+ return new Promise((resolve5) => {
3236
+ lc.waiters.add(() => resolve5(childClosed(child)));
3237
3237
  });
3238
3238
  }
3239
3239
  async function endedOrIgnored(child) {
@@ -3270,7 +3270,7 @@ function signalChildGroup(child, signal) {
3270
3270
  }
3271
3271
  }
3272
3272
  }
3273
- function waitWithin(start, ms, stop) {
3273
+ function waitWithin(start2, ms, stop) {
3274
3274
  if (ms <= 0) {
3275
3275
  return Promise.resolve({ status: "timeout" });
3276
3276
  }
@@ -3278,7 +3278,7 @@ function waitWithin(start, ms, stop) {
3278
3278
  return Promise.resolve({ status: "stopped" });
3279
3279
  }
3280
3280
  const ac = new AbortController();
3281
- return new Promise((resolve4) => {
3281
+ return new Promise((resolve5) => {
3282
3282
  let timer = null;
3283
3283
  let done = false;
3284
3284
  const onStop = () => settle({ status: "stopped" });
@@ -3294,11 +3294,11 @@ function waitWithin(start, ms, stop) {
3294
3294
  if (r.status !== "value" && r.status !== "error") {
3295
3295
  ac.abort();
3296
3296
  }
3297
- resolve4(r);
3297
+ resolve5(r);
3298
3298
  };
3299
3299
  let p;
3300
3300
  try {
3301
- p = start(ac.signal);
3301
+ p = start2(ac.signal);
3302
3302
  } catch (error2) {
3303
3303
  settle({ status: "error", error: error2 });
3304
3304
  return;
@@ -3333,24 +3333,24 @@ function spawnTracked(args, opts) {
3333
3333
  }
3334
3334
  function feedStdin(child, input, onSourceError) {
3335
3335
  let unpipe = () => void 0;
3336
- const done = new Promise((resolve4) => {
3337
- child.stdin.on("error", () => resolve4());
3336
+ const done = new Promise((resolve5) => {
3337
+ child.stdin.on("error", () => resolve5());
3338
3338
  if (input === void 0) {
3339
3339
  child.stdin.end();
3340
- resolve4();
3340
+ resolve5();
3341
3341
  } else if (typeof input === "string" || Buffer.isBuffer(input)) {
3342
- child.stdin.end(input, () => resolve4());
3342
+ child.stdin.end(input, () => resolve5());
3343
3343
  } else {
3344
3344
  input.on("error", () => {
3345
3345
  onSourceError();
3346
- resolve4();
3346
+ resolve5();
3347
3347
  });
3348
3348
  input.pipe(child.stdin);
3349
3349
  unpipe = () => {
3350
3350
  input.unpipe(child.stdin);
3351
- resolve4();
3351
+ resolve5();
3352
3352
  };
3353
- child.stdin.on("finish", () => resolve4());
3353
+ child.stdin.on("finish", () => resolve5());
3354
3354
  child.on("close", unpipe);
3355
3355
  }
3356
3356
  });
@@ -3463,18 +3463,18 @@ function startWatchdog(child, opts, kill, isClosed) {
3463
3463
  const pgid = child.pid;
3464
3464
  let stopped = false;
3465
3465
  const stopper = new AbortController();
3466
- const sleep2 = (ms) => new Promise((resolve4) => {
3466
+ const sleep2 = (ms) => new Promise((resolve5) => {
3467
3467
  const wake = () => {
3468
3468
  clearTimeout(t);
3469
3469
  stopper.signal.removeEventListener("abort", wake);
3470
- resolve4();
3470
+ resolve5();
3471
3471
  };
3472
3472
  const t = setTimeout(wake, ms);
3473
3473
  t.unref();
3474
3474
  stopper.signal.addEventListener("abort", wake, { once: true });
3475
3475
  });
3476
- const sample = async (what, start) => {
3477
- const r = await waitWithin(start, sampleBoundMs, stopper.signal);
3476
+ const sample = async (what, start2) => {
3477
+ const r = await waitWithin(start2, sampleBoundMs, stopper.signal);
3478
3478
  if (isClosed() || stopped) {
3479
3479
  return null;
3480
3480
  }
@@ -3724,7 +3724,7 @@ async function reapChild(child, opts = {}) {
3724
3724
  if (groupCleared || !childClosed(child) || Date.now() >= deadline) {
3725
3725
  break;
3726
3726
  }
3727
- await new Promise((resolve4) => setTimeout(resolve4, Math.min(25, deadline - Date.now())));
3727
+ await new Promise((resolve5) => setTimeout(resolve5, Math.min(25, deadline - Date.now())));
3728
3728
  }
3729
3729
  if (groupCleared === false) {
3730
3730
  timedOut = true;
@@ -3779,12 +3779,12 @@ var import_promises2 = require("node:fs/promises");
3779
3779
  async function sha256File(path) {
3780
3780
  const h = (0, import_node_crypto2.createHash)("sha256");
3781
3781
  let size = 0;
3782
- await new Promise((resolve4, reject) => {
3782
+ await new Promise((resolve5, reject) => {
3783
3783
  (0, import_node_fs2.createReadStream)(path).on("data", (c) => {
3784
3784
  const b = typeof c === "string" ? Buffer.from(c) : c;
3785
3785
  size += b.length;
3786
3786
  h.update(b);
3787
- }).on("error", reject).on("end", () => resolve4());
3787
+ }).on("error", reject).on("end", () => resolve5());
3788
3788
  });
3789
3789
  const st = await (0, import_promises2.stat)(path);
3790
3790
  if (st.size !== size) {
@@ -3803,8 +3803,8 @@ async function indexPackFromFile(dir, packPath, opts = {}) {
3803
3803
  if (opts.maxInputBytes) {
3804
3804
  args.push(`--max-input-size=${opts.maxInputBytes}`);
3805
3805
  }
3806
- const run = { cwd: dir, input: (0, import_node_fs3.createReadStream)(packPath), label: "index-pack", ...opts };
3807
- const res = await runGit(args, { ...run, allowFailure: true });
3806
+ const run2 = { cwd: dir, input: (0, import_node_fs3.createReadStream)(packPath), label: "index-pack", ...opts };
3807
+ const res = await runGit(args, { ...run2, allowFailure: true });
3808
3808
  if (res.code !== 0) {
3809
3809
  const msg = res.stderr.trim().split("\n").pop() ?? "index-pack failed";
3810
3810
  const missing = /did not receive expected object|not a valid|missing/i.test(res.stderr);
@@ -3844,8 +3844,8 @@ async function refTransaction(dir, updates, opts = {}) {
3844
3844
  }
3845
3845
  async function batchCheck(dir, oids, opts = {}) {
3846
3846
  const input = Array.from(oids).join("\n") + "\n";
3847
- const run = { cwd: dir, input, maxStdoutBytes: 512 * 1024 * 1024, ...opts };
3848
- const { stdout } = await runGit(["cat-file", "--batch-check=%(objectname) %(objecttype) %(objectsize)"], run);
3847
+ const run2 = { cwd: dir, input, maxStdoutBytes: 512 * 1024 * 1024, ...opts };
3848
+ const { stdout } = await runGit(["cat-file", "--batch-check=%(objectname) %(objecttype) %(objectsize)"], run2);
3849
3849
  const out = [];
3850
3850
  for (const line of stdout.toString("utf8").split("\n")) {
3851
3851
  if (!line) {
@@ -3867,14 +3867,14 @@ async function resolveRevs(dir, revs, opts = {}) {
3867
3867
  const out = revs.map(
3868
3868
  (r) => r.startsWith("-") ? null : r.includes("\0") ? new PublisherError("INVALID_REQUEST", "NUL in git argument") : null
3869
3869
  );
3870
- const ask4 = revs.flatMap((r, i) => out[i] === null && !r.startsWith("-") ? [i] : []);
3871
- if (ask4.length === 0) {
3870
+ const ask3 = revs.flatMap((r, i) => out[i] === null && !r.startsWith("-") ? [i] : []);
3871
+ if (ask3.length === 0) {
3872
3872
  return out;
3873
3873
  }
3874
- const { stdout } = await runGit(["cat-file", "--batch-check=%(objectname)", "-Z"], { cwd: dir, input: ask4.map((i) => revs[i]).join("\0") + "\0", ...opts });
3874
+ const { stdout } = await runGit(["cat-file", "--batch-check=%(objectname)", "-Z"], { cwd: dir, input: ask3.map((i) => revs[i]).join("\0") + "\0", ...opts });
3875
3875
  const records = stdout.toString("utf8").split("\0");
3876
3876
  const misses = [];
3877
- for (const [j, i] of ask4.entries()) {
3877
+ for (const [j, i] of ask3.entries()) {
3878
3878
  const record2 = records[j] ?? "";
3879
3879
  if (record2 && !/ (missing|ambiguous)$/.test(record2)) {
3880
3880
  out[i] = record2;
@@ -3910,7 +3910,7 @@ async function packObjectsToFile(repoDir, extraArgs, stdinLines, opts) {
3910
3910
  opts.signal?.addEventListener("abort", onAbort, { once: true });
3911
3911
  let failure2 = null;
3912
3912
  try {
3913
- const finished = new Promise((resolve4, reject) => out.on("finish", () => resolve4()).on("error", reject));
3913
+ const finished = new Promise((resolve5, reject) => out.on("finish", () => resolve5()).on("error", reject));
3914
3914
  finished.catch(() => void 0);
3915
3915
  child.stdout.pipe(out);
3916
3916
  child.stdin.on("error", () => void 0);
@@ -3959,8 +3959,8 @@ async function packObjectsToFile(repoDir, extraArgs, stdinLines, opts) {
3959
3959
  }
3960
3960
  function delay(ms) {
3961
3961
  let t;
3962
- const p = new Promise((resolve4) => {
3963
- t = setTimeout(resolve4, ms);
3962
+ const p = new Promise((resolve5) => {
3963
+ t = setTimeout(resolve5, ms);
3964
3964
  t.unref();
3965
3965
  });
3966
3966
  p.cancel = () => clearTimeout(t);
@@ -4109,12 +4109,12 @@ async function commitPaths(dir, paths, opts) {
4109
4109
  if (paths.length === 0) {
4110
4110
  throw new PublisherError("LOCAL_STATE", "no files selected to commit");
4111
4111
  }
4112
- const run = { cwd: dir, env: authorEnv(opts) };
4112
+ const run2 = { cwd: dir, env: authorEnv(opts) };
4113
4113
  if (opts.signal) {
4114
- run.signal = opts.signal;
4114
+ run2.signal = opts.signal;
4115
4115
  }
4116
- await runGit(["add", "--pathspec-from-file=-", "--pathspec-file-nul"], { ...run, input: paths.join("\0") + "\0" });
4117
- await runGit(["commit", "-q", "--no-verify", "-m", opts.message], run);
4116
+ await runGit(["add", "--pathspec-from-file=-", "--pathspec-file-nul"], { ...run2, input: paths.join("\0") + "\0" });
4117
+ await runGit(["commit", "-q", "--no-verify", "-m", opts.message], run2);
4118
4118
  const head = await headCommit(dir);
4119
4119
  if (!head) {
4120
4120
  throw new PublisherError("GIT", "commit did not produce HEAD");
@@ -5062,6 +5062,29 @@ function isRecord(value) {
5062
5062
  return typeof value === "object" && value !== null && !Array.isArray(value);
5063
5063
  }
5064
5064
 
5065
+ // ../client/src/portal-request.ts
5066
+ async function askPortal(fetchImpl, url2, token, body) {
5067
+ try {
5068
+ const res = await fetchImpl(url2, {
5069
+ method: body === void 0 ? "GET" : "POST",
5070
+ headers: { authorization: `Bearer ${token}`, accept: "application/json", ...body === void 0 ? {} : { "content-type": "application/json" } },
5071
+ ...body === void 0 ? {} : { body: JSON.stringify(body) },
5072
+ signal: AbortSignal.timeout(3e4)
5073
+ });
5074
+ const text = await res.text();
5075
+ try {
5076
+ return { status: res.status, body: text ? JSON.parse(text) : null };
5077
+ } catch {
5078
+ return { status: res.status, body: void 0 };
5079
+ }
5080
+ } catch {
5081
+ return { status: 0, body: null };
5082
+ }
5083
+ }
5084
+ function isRecord2(value) {
5085
+ return typeof value === "object" && value !== null && !Array.isArray(value);
5086
+ }
5087
+
5065
5088
  // ../client/src/application.ts
5066
5089
  var APPLICATION_CREATION = "application-creation";
5067
5090
  var ATTEMPTS = 3;
@@ -5076,7 +5099,7 @@ async function createApplication(input, opts) {
5076
5099
  let refreshed = false;
5077
5100
  let lastOutage = "";
5078
5101
  for (let attempt = 0; attempt < ATTEMPTS; ) {
5079
- const answer2 = await post(fetchImpl, url2, await opts.token(), body);
5102
+ const answer2 = await askPortal(fetchImpl, url2, await opts.token(), body);
5080
5103
  if (answer2.status === 401 && !refreshed && opts.refresh !== void 0) {
5081
5104
  refreshed = true;
5082
5105
  if (await opts.refresh()) {
@@ -5102,7 +5125,7 @@ async function createApplication(input, opts) {
5102
5125
  lastOutage = answer2.status === 0 ? "the portal could not be reached" : `the portal answered ${answer2.status}`;
5103
5126
  attempt += 1;
5104
5127
  if (attempt < ATTEMPTS) {
5105
- await new Promise((resolve4) => setTimeout(resolve4, (opts.retryDelayMs ?? 1e3) * attempt));
5128
+ await new Promise((resolve5) => setTimeout(resolve5, (opts.retryDelayMs ?? 1e3) * attempt));
5106
5129
  }
5107
5130
  }
5108
5131
  throw new PublisherError("UNAVAILABLE", `could not make the application: ${lastOutage}; run init again`, { retryable: true });
@@ -5115,27 +5138,6 @@ function read(body, created) {
5115
5138
  }
5116
5139
  return { appId: application["appId"], created, repository: repository.data };
5117
5140
  }
5118
- async function post(fetchImpl, url2, token, body) {
5119
- try {
5120
- const res = await fetchImpl(url2, {
5121
- method: "POST",
5122
- headers: { authorization: `Bearer ${token}`, accept: "application/json", "content-type": "application/json" },
5123
- body: JSON.stringify(body),
5124
- signal: AbortSignal.timeout(3e4)
5125
- });
5126
- const text = await res.text();
5127
- try {
5128
- return { status: res.status, body: text ? JSON.parse(text) : null };
5129
- } catch {
5130
- return { status: res.status, body: void 0 };
5131
- }
5132
- } catch {
5133
- return { status: 0, body: null };
5134
- }
5135
- }
5136
- function isRecord2(value) {
5137
- return typeof value === "object" && value !== null && !Array.isArray(value);
5138
- }
5139
5141
 
5140
5142
  // ../client/src/transport.ts
5141
5143
  function assertCredentialEndpoint(endpoint) {
@@ -5251,8 +5253,8 @@ function fallbackCode(status2) {
5251
5253
  return status2 >= 500 ? "UNAVAILABLE" : "INVALID_REQUEST";
5252
5254
  }
5253
5255
  function sleep(ms, signal) {
5254
- return new Promise((resolve4, reject) => {
5255
- const t = setTimeout(resolve4, ms);
5256
+ return new Promise((resolve5, reject) => {
5257
+ const t = setTimeout(resolve5, ms);
5256
5258
  signal?.addEventListener(
5257
5259
  "abort",
5258
5260
  () => {
@@ -5268,14 +5270,14 @@ function sleep(ms, signal) {
5268
5270
  var RUN_STAGES = ["received", "running", "deploying", "live"];
5269
5271
  var STAGE_WORDS = { received: "received", running: "checked", deploying: "going live", live: "live" };
5270
5272
  var STATES = ["pending", "in-progress", "done", "failed", "skipped"];
5271
- function readStages(run) {
5272
- if (!isRecord3(run)) {
5273
+ function readStages(run2) {
5274
+ if (!isRecord3(run2)) {
5273
5275
  return { malformed: "a run that is not an object" };
5274
5276
  }
5275
- if (!Object.hasOwn(run, "stages")) {
5277
+ if (!Object.hasOwn(run2, "stages")) {
5276
5278
  return null;
5277
5279
  }
5278
- const stages = run["stages"];
5280
+ const stages = run2["stages"];
5279
5281
  if (!Array.isArray(stages) || stages.length !== RUN_STAGES.length) {
5280
5282
  return { malformed: "stages that are not exactly four" };
5281
5283
  }
@@ -5345,12 +5347,12 @@ async function followRun(input, opts) {
5345
5347
  return { kind: "forbidden" };
5346
5348
  }
5347
5349
  if (answer2.status === 200) {
5348
- const run = soleRun(answer2.body);
5349
- if ("detail" in run) {
5350
- return { kind: "malformed", detail: run.detail };
5350
+ const run2 = soleRun(answer2.body);
5351
+ if ("detail" in run2) {
5352
+ return { kind: "malformed", detail: run2.detail };
5351
5353
  }
5352
- if (run.run !== null) {
5353
- const read2 = readStages(run.run);
5354
+ if (run2.run !== null) {
5355
+ const read2 = readStages(run2.run);
5354
5356
  if (read2 === null) {
5355
5357
  return { kind: "unavailable", reason: "no-stages" };
5356
5358
  }
@@ -5367,12 +5369,12 @@ async function followRun(input, opts) {
5367
5369
  opts.onStage?.(entry);
5368
5370
  moved = true;
5369
5371
  }
5370
- if (isRecord3(run.run) && run.run["status"] === "awaiting-approval") {
5372
+ if (isRecord3(run2.run) && run2.run["status"] === "awaiting-approval") {
5371
5373
  return { kind: "waiting", stages: read2.stages };
5372
5374
  }
5373
5375
  const end = ending(read2.stages);
5374
5376
  if (end !== null) {
5375
- const runId = isRecord3(run.run) && typeof run.run["runId"] === "string" ? run.run["runId"] : null;
5377
+ const runId = isRecord3(run2.run) && typeof run2.run["runId"] === "string" ? run2.run["runId"] : null;
5376
5378
  return { kind: "settled", ending: end, failedAt: read2.stages.find((s) => s.state === "failed")?.word ?? null, stages: read2.stages, runId };
5377
5379
  }
5378
5380
  if (opts.untilChange === true && moved && answered) {
@@ -5468,7 +5470,7 @@ async function call(input, opts, body) {
5468
5470
  const url2 = `${input.portal.replace(/\/+$/, "")}/api/v1/terminal/repositories/${encodeURIComponent(input.repoId)}/preparation`;
5469
5471
  let refreshed = false;
5470
5472
  for (; ; ) {
5471
- const answer2 = await ask2(fetchImpl, url2, await opts.token(), body);
5473
+ const answer2 = await askPortal(fetchImpl, url2, await opts.token(), body);
5472
5474
  if (answer2.status === 401 && !refreshed && opts.refresh !== void 0) {
5473
5475
  refreshed = true;
5474
5476
  if (await opts.refresh()) {
@@ -5495,7 +5497,7 @@ async function call(input, opts, body) {
5495
5497
  }
5496
5498
  }
5497
5499
  function readView(body) {
5498
- if (!isRecord4(body)) {
5500
+ if (!isRecord2(body)) {
5499
5501
  return { detail: "an answer that is not a preparation" };
5500
5502
  }
5501
5503
  const { id, revision, status: status2, message, questions, publication } = body;
@@ -5516,12 +5518,12 @@ function readView(body) {
5516
5518
  }
5517
5519
  const read2 = [];
5518
5520
  for (const q of questions) {
5519
- if (!isRecord4(q) || typeof q["id"] !== "string" || typeof q["text"] !== "string" || !Array.isArray(q["options"]) || q["options"].length === 0) {
5521
+ if (!isRecord2(q) || typeof q["id"] !== "string" || typeof q["text"] !== "string" || !Array.isArray(q["options"]) || q["options"].length === 0) {
5520
5522
  return { detail: "a question that cannot be answered" };
5521
5523
  }
5522
5524
  const options = [];
5523
5525
  for (const o of q["options"]) {
5524
- if (!isRecord4(o) || typeof o["value"] !== "string" || typeof o["label"] !== "string") {
5526
+ if (!isRecord2(o) || typeof o["value"] !== "string" || typeof o["label"] !== "string") {
5525
5527
  return { detail: "a question with an unreadable option" };
5526
5528
  }
5527
5529
  options.push({ value: o["value"], label: o["label"] });
@@ -5529,7 +5531,7 @@ function readView(body) {
5529
5531
  read2.push({ id: q["id"], text: q["text"], options });
5530
5532
  }
5531
5533
  let pub = null;
5532
- if (isRecord4(publication) && typeof publication["status"] === "string" && typeof publication["message"] === "string") {
5534
+ if (isRecord2(publication) && typeof publication["status"] === "string" && typeof publication["message"] === "string") {
5533
5535
  pub = { status: publication["status"], message: publication["message"], ...typeof publication["url"] === "string" ? { url: publication["url"] } : {} };
5534
5536
  }
5535
5537
  return { id, revision, status: status2, message, questions: read2, publication: pub, findings: readFindings(body["findings"]) };
@@ -5544,7 +5546,7 @@ function readFindings(value) {
5544
5546
  if (out.length === MAX_FINDINGS) {
5545
5547
  break;
5546
5548
  }
5547
- if (!isRecord4(f)) {
5549
+ if (!isRecord2(f)) {
5548
5550
  continue;
5549
5551
  }
5550
5552
  const kind = text(f["kind"]);
@@ -5566,27 +5568,41 @@ function readFindings(value) {
5566
5568
  }
5567
5569
  return out;
5568
5570
  }
5569
- async function ask2(fetchImpl, url2, token, body) {
5570
- try {
5571
- const res = await fetchImpl(url2, {
5572
- method: body === void 0 ? "GET" : "POST",
5573
- headers: { authorization: `Bearer ${token}`, accept: "application/json", ...body === void 0 ? {} : { "content-type": "application/json" } },
5574
- ...body === void 0 ? {} : { body: JSON.stringify(body) },
5575
- signal: AbortSignal.timeout(3e4)
5576
- });
5577
- const text = await res.text();
5578
- try {
5579
- return { status: res.status, body: text ? JSON.parse(text) : null };
5580
- } catch {
5581
- return { status: res.status, body: void 0 };
5571
+
5572
+ // ../client/src/creator-tools.ts
5573
+ var CREATOR_TOOLS = "creator-tools";
5574
+ async function runCreatorTool(input, opts) {
5575
+ const fetchImpl = opts.fetch ?? fetch;
5576
+ const url2 = `${input.portal.replace(/\/+$/, "")}/api/v1/terminal/tools/${encodeURIComponent(input.tool)}`;
5577
+ let refreshed = false;
5578
+ for (; ; ) {
5579
+ const answer2 = await askPortal(fetchImpl, url2, await opts.token(), input.args);
5580
+ if (answer2.status === 401 && !refreshed && opts.refresh !== void 0) {
5581
+ refreshed = true;
5582
+ if (await opts.refresh()) {
5583
+ continue;
5584
+ }
5582
5585
  }
5583
- } catch {
5584
- return { status: 0, body: null };
5586
+ if (answer2.status === 401) {
5587
+ return { kind: "unauthorized" };
5588
+ }
5589
+ if (answer2.status === 403) {
5590
+ return { kind: "forbidden" };
5591
+ }
5592
+ if (answer2.status === 404) {
5593
+ return { kind: "unavailable", detail: "the portal does not serve this tool" };
5594
+ }
5595
+ if (answer2.status !== 200) {
5596
+ return { kind: "unavailable", detail: answer2.status === 0 ? "the portal could not be reached" : `the portal answered ${answer2.status}` };
5597
+ }
5598
+ const body = answer2.body;
5599
+ if (!isRecord2(body) || typeof body["say"] !== "string" || typeof body["next"] !== "string") {
5600
+ return { kind: "malformed", detail: "an answer with no say and next" };
5601
+ }
5602
+ const { say, next, isError, ...fields } = body;
5603
+ return { kind: "answer", answer: { say, next, isError: isError === true, fields } };
5585
5604
  }
5586
5605
  }
5587
- function isRecord4(value) {
5588
- return typeof value === "object" && value !== null && !Array.isArray(value);
5589
- }
5590
5606
 
5591
5607
  // ../client/src/onboard.ts
5592
5608
  var import_promises6 = require("node:fs/promises");
@@ -6374,6 +6390,14 @@ var Publisher = class {
6374
6390
  async answerPreparation(input) {
6375
6391
  return answerPreparation(input, this.portalCall());
6376
6392
  }
6393
+ /**
6394
+ * One of the creator's tools at the portal that serves this endpoint — starters, status, history,
6395
+ * going back, suggestions, sharing — with this publisher's credential and its one refresh. The
6396
+ * caller confirms the portal's `/config` lists `CREATOR_TOOLS` first.
6397
+ */
6398
+ async runCreatorTool(input) {
6399
+ return runCreatorTool(input, this.portalCall());
6400
+ }
6377
6401
  portalCall() {
6378
6402
  return {
6379
6403
  token: this.tokenFn,
@@ -6782,6 +6806,11 @@ var OPTIONS = {
6782
6806
  theirs: { type: "string" },
6783
6807
  to: { type: "string" },
6784
6808
  cursor: { type: "string" },
6809
+ replaces: { type: "string" },
6810
+ run: { type: "string" },
6811
+ answers: { type: "string" },
6812
+ add: { type: "string", multiple: true },
6813
+ remove: { type: "string", multiple: true },
6785
6814
  ref: { type: "string", multiple: true },
6786
6815
  json: { type: "boolean" },
6787
6816
  quiet: { type: "boolean" },
@@ -6946,13 +6975,13 @@ function stageReporter(io) {
6946
6975
  // src/follow.ts
6947
6976
  async function followPublication(p, endpoint, o, opts) {
6948
6977
  const derived = portalForEndpoint(endpoint);
6949
- const confirmed = derived === null ? null : await confirmPortal(derived, endpoint, opts.fetch);
6950
- const portal = confirmed === null ? null : derived;
6978
+ const confirmed2 = derived === null ? null : await confirmPortal(derived, endpoint, opts.fetch);
6979
+ const portal = confirmed2 === null ? null : derived;
6951
6980
  const elsewhere = (reason) => ({ kind: "unavailable", reason, portal, hint: inPortal(portal, "Published.", "Follow it") });
6952
- if (portal === null || confirmed === null) {
6981
+ if (portal === null || confirmed2 === null) {
6953
6982
  return elsewhere("no-portal");
6954
6983
  }
6955
- if (!confirmed.terminal.includes(RUN_PROGRESS)) {
6984
+ if (!confirmed2.terminal.includes(RUN_PROGRESS)) {
6956
6985
  const noRoute = { kind: "unavailable", reason: "no-route" };
6957
6986
  return { ...noRoute, portal, hint: progressHint(noRoute, portal) };
6958
6987
  }
@@ -6966,7 +6995,7 @@ async function followPublication(p, endpoint, o, opts) {
6966
6995
  { portal, repoId: o.repoId, publicationId: o.publicationId, ref, commit },
6967
6996
  { onStage: opts.onStage, ...opts.timeoutMs === void 0 ? {} : { timeoutMs: opts.timeoutMs }, ...opts.untilChange === true ? { untilChange: true } : {} }
6968
6997
  ).finally(() => opts.stopStages?.());
6969
- const answerable = confirmed.terminal.includes(PREPARATION);
6998
+ const answerable = confirmed2.terminal.includes(PREPARATION);
6970
6999
  const dir = shellWord(opts.dir);
6971
7000
  const next = outcome2.kind === "timed-out" || outcome2.kind === "moving" ? { action: "follow", command: `publisher follow ${dir} --publication ${o.publicationId}${opts.untilChange === true ? " --until-change" : ""} --json` } : outcome2.kind === "waiting" && answerable ? { action: "question", command: `publisher question ${dir} --json` } : void 0;
6972
7001
  const findings = await failedChecks(p, portal, o.repoId, outcome2, answerable);
@@ -7080,7 +7109,7 @@ function resolveChoice(answer2, names) {
7080
7109
  }
7081
7110
  return names.find((n) => n.toLowerCase() === a.toLowerCase()) ?? null;
7082
7111
  }
7083
- async function ask3(question2) {
7112
+ async function ask2(question2) {
7084
7113
  const rl = (0, import_promises11.createInterface)({ input: process.stdin, output: process.stderr });
7085
7114
  try {
7086
7115
  return await rl.question(question2);
@@ -7098,7 +7127,7 @@ ${purpose}
7098
7127
  names.forEach((n, i) => process.stderr.write(` ${i + 1}) ${n}
7099
7128
  `));
7100
7129
  for (let attempt = 0; attempt < 3; attempt++) {
7101
- const answer2 = await ask3(`
7130
+ const answer2 = await ask2(`
7102
7131
  Which one? [1-${names.length}, or a name] `);
7103
7132
  if (answer2 === null) {
7104
7133
  return null;
@@ -7118,7 +7147,7 @@ Name this connection, so you never have to type ${endpoint} again.
7118
7147
  (for example: prod, dev, staging)
7119
7148
  `);
7120
7149
  for (let attempt = 0; attempt < 3; attempt++) {
7121
- const answer2 = await ask3(suggested ? `
7150
+ const answer2 = await ask2(suggested ? `
7122
7151
  Profile name [${suggested}]: ` : `
7123
7152
  Profile name: `);
7124
7153
  if (answer2 === null) {
@@ -7407,14 +7436,14 @@ function packageVersion() {
7407
7436
  }
7408
7437
  }
7409
7438
  function claude(args) {
7410
- return new Promise((resolve4) => {
7439
+ return new Promise((resolve5) => {
7411
7440
  let stdout = "";
7412
7441
  let stderr = "";
7413
7442
  const child = (0, import_node_child_process3.spawn)("claude", args, { stdio: ["ignore", "pipe", "pipe"] });
7414
7443
  child.stdout.on("data", (d) => stdout += d.toString("utf8"));
7415
7444
  child.stderr.on("data", (d) => stderr += d.toString("utf8"));
7416
- child.on("error", (e) => resolve4({ code: null, stdout, stderr: e.message, missing: e.code === "ENOENT" }));
7417
- child.on("close", (code) => resolve4({ code, stdout, stderr, missing: false }));
7445
+ child.on("error", (e) => resolve5({ code: null, stdout, stderr: e.message, missing: e.code === "ENOENT" }));
7446
+ child.on("close", (code) => resolve5({ code, stdout, stderr, missing: false }));
7418
7447
  });
7419
7448
  }
7420
7449
  async function installSkills({ log }) {
@@ -7555,11 +7584,11 @@ async function repositoryForInit(p, endpoint, name, opts = {}) {
7555
7584
  if (portal === null) {
7556
7585
  return { repoId: (await p.createRepository({ name, grants: [] })).repoId, appId: null };
7557
7586
  }
7558
- const confirmed = await confirmPortal(portal, endpoint, opts.fetch);
7559
- if (confirmed === null) {
7587
+ const confirmed2 = await confirmPortal(portal, endpoint, opts.fetch);
7588
+ if (confirmed2 === null) {
7560
7589
  throw new PublisherError("UNAVAILABLE", `could not confirm that ${portal} publishes to ${endpoint}; run init again`, { retryable: true });
7561
7590
  }
7562
- if (!confirmed.terminal.includes(APPLICATION_CREATION)) {
7591
+ if (!confirmed2.terminal.includes(APPLICATION_CREATION)) {
7563
7592
  throw new PublisherError("UNAVAILABLE", `${portal} cannot make an application from a terminal yet, and a repository needs one; ask whoever runs it to update the portal`);
7564
7593
  }
7565
7594
  const made = await p.createApplication({ portal, name });
@@ -7716,6 +7745,24 @@ function folderFor(name) {
7716
7745
  return slug2 === "" ? "app" : slug2;
7717
7746
  }
7718
7747
 
7748
+ // src/portal.ts
7749
+ var confirmed = /* @__PURE__ */ new Map();
7750
+ async function portalFor(endpoint, route, opts = {}) {
7751
+ const derived = portalForEndpoint(endpoint);
7752
+ if (derived === null) {
7753
+ return { kind: "no-portal" };
7754
+ }
7755
+ const asked = opts.fetch === void 0 ? confirmed.get(endpoint) ?? confirmPortal(derived, endpoint) : confirmPortal(derived, endpoint, opts.fetch);
7756
+ if (opts.fetch === void 0) {
7757
+ confirmed.set(endpoint, asked);
7758
+ }
7759
+ const config2 = await asked;
7760
+ if (config2 === null) {
7761
+ return { kind: "no-portal" };
7762
+ }
7763
+ return config2.terminal.includes(route) ? { kind: "served", portal: derived } : { kind: "no-route", portal: derived };
7764
+ }
7765
+
7719
7766
  // src/question.ts
7720
7767
  async function questionAt(p, endpoint, repoId, opts = {}) {
7721
7768
  const at = await portalAt(endpoint, opts);
@@ -7766,15 +7813,20 @@ function parseSpecs(specs) {
7766
7813
  return answers;
7767
7814
  }
7768
7815
  async function portalAt(endpoint, opts) {
7769
- const derived = portalForEndpoint(endpoint);
7770
- const confirmed = derived === null ? null : await confirmPortal(derived, endpoint, opts.fetch);
7771
- if (derived === null || confirmed === null) {
7772
- return { kind: "unavailable", reason: "no-portal", portal: null, hint: "Answer it in the portal." };
7773
- }
7774
- if (!confirmed.terminal.includes(PREPARATION)) {
7775
- return { kind: "unavailable", reason: "no-route", portal: derived, hint: `This portal cannot show the question in the terminal yet. Answer it in the portal: ${derived}.` };
7816
+ const at = await portalFor(endpoint, PREPARATION, opts);
7817
+ switch (at.kind) {
7818
+ case "served":
7819
+ return { portal: at.portal };
7820
+ case "no-portal":
7821
+ return { kind: "unavailable", reason: "no-portal", portal: null, hint: "Answer it in the portal." };
7822
+ case "no-route":
7823
+ return {
7824
+ kind: "unavailable",
7825
+ reason: "no-route",
7826
+ portal: at.portal,
7827
+ hint: `This portal cannot show the question in the terminal yet. Answer it in the portal: ${at.portal}.`
7828
+ };
7776
7829
  }
7777
- return { portal: derived };
7778
7830
  }
7779
7831
  function shown(portal, r) {
7780
7832
  switch (r.kind) {
@@ -7843,6 +7895,116 @@ var answer = {
7843
7895
  }
7844
7896
  };
7845
7897
 
7898
+ // src/commands/creator.ts
7899
+ var import_node_path10 = require("node:path");
7900
+
7901
+ // src/creator.ts
7902
+ async function creatorTool(p, endpoint, tool, args, opts = {}) {
7903
+ const at = await portalFor(endpoint, CREATOR_TOOLS, opts);
7904
+ if (at.kind === "no-portal") {
7905
+ throw new PublisherError("UNAVAILABLE", "no portal answers for this endpoint, so this can only be done in the chat or the portal");
7906
+ }
7907
+ if (at.kind === "no-route") {
7908
+ throw new PublisherError("UNAVAILABLE", `this portal cannot do this from the terminal yet; do it in the chat or at ${at.portal}`);
7909
+ }
7910
+ const result = await p.runCreatorTool({ portal: at.portal, tool, args });
7911
+ switch (result.kind) {
7912
+ case "answer":
7913
+ return result.answer;
7914
+ case "unauthorized":
7915
+ throw new PublisherError("UNAUTHORIZED", "the portal did not accept this sign-in; run publisher login");
7916
+ case "forbidden":
7917
+ throw new PublisherError("FORBIDDEN", "the portal will not let this sign-in do that in this organisation");
7918
+ case "unavailable":
7919
+ throw new PublisherError("UNAVAILABLE", `the portal could not be asked: ${result.detail}`, { retryable: true });
7920
+ case "malformed":
7921
+ throw new PublisherError("UNAVAILABLE", `the portal's answer could not be read: ${result.detail}`);
7922
+ }
7923
+ }
7924
+ function printed(answer2) {
7925
+ return { say: answer2.say, next: answer2.next, refused: answer2.isError, ...answer2.fields };
7926
+ }
7927
+
7928
+ // src/commands/creator.ts
7929
+ async function run(inv, dir, tool, args) {
7930
+ const { p, endpoint } = await inv.client(dir);
7931
+ const answer2 = await creatorTool(p, endpoint, tool, args);
7932
+ inv.out(printed(answer2));
7933
+ return answer2.isError ? 1 : 0;
7934
+ }
7935
+ async function folderApp(inv) {
7936
+ const dir = (0, import_node_path10.resolve)(".");
7937
+ return { dir, app: await inv.repoId(dir) };
7938
+ }
7939
+ function listFlag(inv, name) {
7940
+ const value = inv.flags[name];
7941
+ const parts = Array.isArray(value) ? value : typeof value === "string" ? [value] : [];
7942
+ return parts.flatMap((v) => v.split(",")).map((v) => v.trim()).filter((v) => v !== "");
7943
+ }
7944
+ var starters = {
7945
+ git: false,
7946
+ run: (inv) => run(inv, void 0, "list_starters", { need: inv.cmd.slice(1).join(" ") })
7947
+ };
7948
+ var start = {
7949
+ git: false,
7950
+ async run(inv) {
7951
+ const starter = inv.cmd[1];
7952
+ if (starter === void 0) {
7953
+ throw new PublisherError("INVALID_REQUEST", "start takes a starter id; publisher starters lists them");
7954
+ }
7955
+ return run(inv, void 0, "use_starter", { starter, ...inv.optStr("name"), ...inv.optStr("replaces") });
7956
+ }
7957
+ };
7958
+ var history = {
7959
+ git: false,
7960
+ async run(inv) {
7961
+ const { dir, app } = await folderApp(inv);
7962
+ return run(inv, dir, "app_history", { app });
7963
+ }
7964
+ };
7965
+ var goBack = {
7966
+ git: false,
7967
+ async run(inv) {
7968
+ const version2 = Number(inv.cmd[1]);
7969
+ if (!Number.isSafeInteger(version2) || version2 < 1) {
7970
+ throw new PublisherError("INVALID_REQUEST", "go-back takes a version number; publisher history lists them");
7971
+ }
7972
+ const { dir, app } = await folderApp(inv);
7973
+ return run(inv, dir, "go_back", { app, version: version2 });
7974
+ }
7975
+ };
7976
+ var suggestions = {
7977
+ git: false,
7978
+ async run(inv) {
7979
+ const { dir, app } = await folderApp(inv);
7980
+ return run(inv, dir, "app_suggestions", { app });
7981
+ }
7982
+ };
7983
+ var suggestion = {
7984
+ git: false,
7985
+ async run(inv) {
7986
+ const [, verb, id] = inv.cmd;
7987
+ if (verb !== "done" && verb !== "decline" || id === void 0) {
7988
+ throw new PublisherError("INVALID_REQUEST", "suggestion takes done or decline, and a suggestion id from publisher suggestions");
7989
+ }
7990
+ const { dir, app } = await folderApp(inv);
7991
+ const status2 = verb === "done" ? "done" : "declined";
7992
+ const runId = inv.str("run");
7993
+ return run(inv, dir, "resolve_suggestion", { app, suggestion: id, status: status2, ...runId === void 0 ? {} : { runId } });
7994
+ }
7995
+ };
7996
+ var share = {
7997
+ git: false,
7998
+ async run(inv) {
7999
+ const access2 = inv.cmd[1];
8000
+ if (access2 === void 0) {
8001
+ throw new PublisherError("INVALID_REQUEST", "share takes organisation, invited or anyone-with-link");
8002
+ }
8003
+ const { dir, app } = await folderApp(inv);
8004
+ return run(inv, dir, "share_app", { app, access: access2, add: listFlag(inv, "add"), remove: listFlag(inv, "remove") });
8005
+ }
8006
+ };
8007
+
7846
8008
  // src/commands/sync.ts
7847
8009
  var sync = {
7848
8010
  git: true,
@@ -7861,6 +8023,7 @@ var sync = {
7861
8023
  const acceptedAt = sending.done();
7862
8024
  const o = r.published;
7863
8025
  const followed = o !== null && o.state === "accepted" && inv.flags["no-wait"] !== true ? await followPublication(p, endpoint, o, { ...inv.following, timeoutMs, dir }) : null;
8026
+ const answered = await answerSuggestion(inv, p, endpoint, linked?.repoId ?? (await readAssociation(dir))?.repoId, o);
7864
8027
  const syncTimings = clock.read(followed !== null && "stages" in followed ? runTimings(followed.stages, acceptedAt) : {});
7865
8028
  inv.log(timingsLine(syncTimings));
7866
8029
  inv.out({
@@ -7869,11 +8032,26 @@ var sync = {
7869
8032
  linked: linked === null ? null : { repoId: linked.repoId, appId: linked.appId, created: linked.created },
7870
8033
  ...outcome(r),
7871
8034
  progress: followed,
8035
+ ...answered === null ? {} : { answered },
7872
8036
  timings: syncTimings
7873
8037
  });
7874
8038
  return r.state === "published" || r.state === "up_to_date" ? 0 : r.state === "unknown" ? 3 : 2;
7875
8039
  }
7876
8040
  };
8041
+ async function answerSuggestion(inv, p, endpoint, repoId, o) {
8042
+ const id = inv.str("answers");
8043
+ if (id === void 0 || repoId === void 0 || o === null || o.state !== "accepted" || o.publicationId === null) {
8044
+ return null;
8045
+ }
8046
+ const ref = "refs/heads/main";
8047
+ const commit = o.refs?.[ref];
8048
+ if (commit === void 0) {
8049
+ return null;
8050
+ }
8051
+ return printed(
8052
+ await creatorTool(p, endpoint, "resolve_suggestion", { app: repoId, suggestion: id, status: "done", publication: { publicationId: o.publicationId, ref, commit } })
8053
+ );
8054
+ }
7877
8055
  function outcome(r) {
7878
8056
  const o = r.published;
7879
8057
  return {
@@ -8009,7 +8187,7 @@ function exitCodeFor(code) {
8009
8187
  }
8010
8188
 
8011
8189
  // src/invocation.ts
8012
- var import_node_path10 = require("node:path");
8190
+ var import_node_path11 = require("node:path");
8013
8191
  var Invocation = class {
8014
8192
  cmd;
8015
8193
  flags;
@@ -8052,7 +8230,7 @@ var Invocation = class {
8052
8230
  };
8053
8231
  /** The folder named at position `i` of the command path, or the current one. */
8054
8232
  dirArg(i) {
8055
- return (0, import_node_path10.resolve)(this.cmd[i] ?? ".");
8233
+ return (0, import_node_path11.resolve)(this.cmd[i] ?? ".");
8056
8234
  }
8057
8235
  /** A flag's value when it is a string, else undefined. */
8058
8236
  str(key) {
@@ -8109,15 +8287,15 @@ async function demand(r, input) {
8109
8287
  }
8110
8288
  const next = nextStep(credentialStatus(input));
8111
8289
  const details = { profiles: r.kind === "none" ? [] : r.profiles, next };
8112
- const run = next.command === null ? "" : ` Run: ${next.command}`;
8290
+ const run2 = next.command === null ? "" : ` Run: ${next.command}`;
8113
8291
  switch (r.kind) {
8114
8292
  case "unknown-profile":
8115
- throw new PublisherError("LOCAL_STATE", `no such profile: ${r.name}.${run}`, { details });
8293
+ throw new PublisherError("LOCAL_STATE", `no such profile: ${r.name}.${run2}`, { details });
8116
8294
  case "ambiguous":
8117
- throw new PublisherError("LOCAL_STATE", `several connections and none is the default: pass --profile <name>, or choose one once.${run}`, { details });
8295
+ throw new PublisherError("LOCAL_STATE", `several connections and none is the default: pass --profile <name>, or choose one once.${run2}`, { details });
8118
8296
  case "none": {
8119
8297
  const where = await whereCredentialsWere();
8120
- throw new PublisherError("LOCAL_STATE", `not connected to a publishing service yet: ${where.sentence}${run}`, { details: { ...details, credentialsFile: where.details } });
8298
+ throw new PublisherError("LOCAL_STATE", `not connected to a publishing service yet: ${where.sentence}${run2}`, { details: { ...details, credentialsFile: where.details } });
8121
8299
  }
8122
8300
  }
8123
8301
  }
@@ -8164,12 +8342,22 @@ Usage:
8164
8342
  checked -> going live -> live, up to 15 minutes)
8165
8343
  [--timeout <seconds>] follow for at most this long; if it is still going, the
8166
8344
  result's next.command picks it up
8345
+ [--answers <suggestion>] the publication answers that suggestion: close it as done
8167
8346
  publisher follow [dir] --publication <id> follow an accepted publication's run where a sync left it
8168
8347
  [--timeout <seconds>] [--until-change] --until-change (sync too): return at the next step, to tell it
8169
8348
  publisher question [dir] [--repo <id>] the question the app's preparation waits on, if any,
8170
8349
  with each option's value (answering needs owner or admin)
8171
8350
  publisher answer <question>=<value>... [--repo <id>] answer it from this folder; a value that is not one of
8172
8351
  the question's options is refused before anything is sent
8352
+ publisher starters [what you want to stop doing] working apps to start from, the ones that fit first
8353
+ publisher start <starter> [--name <n>] [--replaces <what>] a new app made from a starter, live in about a minute
8354
+ publisher history this folder's app: the versions that went live, newest first
8355
+ publisher go-back <version> put the code of an earlier version back, as the next version
8356
+ (the data and who can open the app stay as they are)
8357
+ publisher suggestions what colleagues suggested for this folder's app, still open
8358
+ publisher suggestion done|decline <id> [--run <id>] close one; sync --answers <id> closes it with that publication
8359
+ publisher share organisation|invited|anyone-with-link [--add <emails>] [--remove <emails>]
8360
+ who can open this folder's app; new invitees get the link by mail
8173
8361
  publisher merge-check [dir] --ours <ref> --theirs <ref> [--apply]
8174
8362
  publisher info [dir] repository state as seen by the server
8175
8363
  publisher doctor environment check (node, git, credentials, association)
@@ -8213,7 +8401,14 @@ var COMMANDS = /* @__PURE__ */ new Map([
8213
8401
  ["answer", answer],
8214
8402
  ["skills", skills],
8215
8403
  ["merge-check", mergeCheck2],
8216
- ["info", info]
8404
+ ["info", info],
8405
+ ["starters", starters],
8406
+ ["start", start],
8407
+ ["history", history],
8408
+ ["go-back", goBack],
8409
+ ["suggestions", suggestions],
8410
+ ["suggestion", suggestion],
8411
+ ["share", share]
8217
8412
  ]);
8218
8413
  async function main(argv) {
8219
8414
  const inv = new Invocation(parseArgs(argv));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@minnovate/shadow-publisher",
3
- "version": "0.11.11",
3
+ "version": "0.12.1",
4
4
  "description": "publisher: publish an app's Git history to the serverless Git backend",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",