@niroai/niro 0.9.0 → 0.10.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.
@@ -55,7 +55,7 @@ async function loginWithApiKey({ apiUrl }) {
55
55
  return;
56
56
  }
57
57
 
58
- const sp = spinner.start("Authenticating...");
58
+ const sp = spinner.start(" Authenticating...");
59
59
  try {
60
60
  const data = await api.post(
61
61
  "/api/public/auth/api-key-login",
@@ -157,11 +157,16 @@ async function loginWithDeviceFlow({ apiUrl }) {
157
157
  throw new Error("Server did not return a complete device-code payload.");
158
158
  }
159
159
 
160
- console.log(`\n Your one-time code: ${color.bold(color.cyan(userCode))}`);
161
- console.log(` Open: ${color.cyan(verificationUri)}\n`);
160
+ // The verification URI is the web app's own address, chosen by whichever server we are
161
+ // talking to. Recording it is the only way the CLI can later link someone to their project
162
+ // without hardcoding a host that is wrong for UAT and wrong for every self-hosted install.
163
+ rememberAppUrl(verificationUri);
164
+
165
+ console.log(`\n Your one-time code: ${color.bold(color.cyan(userCode))}`);
166
+ console.log(` Open: ${color.cyan(verificationUri)}\n`);
162
167
  browser.open(verificationUri);
163
168
 
164
- const sp = spinner.start("Waiting for browser approval...");
169
+ const sp = spinner.start(" Waiting for browser approval...");
165
170
  const deadline = Date.now() + expiresIn * 1000;
166
171
  try {
167
172
  while (Date.now() < deadline) {
@@ -199,6 +204,21 @@ async function loginWithDeviceFlow({ apiUrl }) {
199
204
  }
200
205
  }
201
206
 
207
+ /**
208
+ * Store the web app's origin, taken from the device-flow verification URI.
209
+ *
210
+ * Best-effort by design: a malformed or missing URI must never fail a login that is otherwise
211
+ * fine. The only cost of not recording it is that `niro start` ends without a browser link.
212
+ */
213
+ function rememberAppUrl(verificationUri) {
214
+ try {
215
+ const origin = new URL(verificationUri).origin;
216
+ require("../lib/config").save({ appUrl: origin });
217
+ } catch {
218
+ // Not a URL we can parse. Leave the previous value alone rather than storing a guess.
219
+ }
220
+ }
221
+
202
222
  function isPending(err) {
203
223
  const body = err.body;
204
224
  if (!body) return false;
@@ -211,4 +231,4 @@ function sleep(ms) {
211
231
  return new Promise((resolve) => setTimeout(resolve, ms));
212
232
  }
213
233
 
214
- module.exports = { login };
234
+ module.exports = { login, rememberAppUrl };
@@ -69,7 +69,7 @@ async function projects(opts = {}) {
69
69
  const repos = `${r.repo_count} repo${r.repo_count === 1 ? "" : "s"}`;
70
70
  const created = fmtDate(r.created_at);
71
71
  const meta = [repos, created].filter(Boolean).join(", ");
72
- console.log(` ${name} ${color.dim(r.project_id)} ${color.dim("— " + meta)}`);
72
+ console.log(` ${name} ${color.dim(r.project_id)} ${color.dim("— " + meta)}`);
73
73
  }
74
74
  }
75
75
 
@@ -141,11 +141,11 @@ async function release(pathArg, opts = {}) {
141
141
  } catch { /* can't inspect — don't block on it */ }
142
142
  }
143
143
  if (stillDiverged.length) {
144
- console.log(warn(`\n ⚠ This temporary project still covers in-flight work:`));
144
+ console.log(warn(`\n ⚠ This temporary project still covers in-flight work:`));
145
145
  for (const d of stillDiverged) {
146
146
  console.log(warn(` ${d.folder} is on \`${d.branch}\` (source: \`${d.sourceBranch}\`)`));
147
147
  }
148
- console.log(warn(" Discarding deletes the WHOLE temporary project — those repos lose their coverage" +
148
+ console.log(warn(" Discarding deletes the WHOLE temporary project — those repos lose their coverage" +
149
149
  " until you run `niro new-temp-project` there again (local code is untouched)."));
150
150
  if (!opts.yes) {
151
151
  if (!process.stdout.isTTY || prompt.isNonInteractive()) {
@@ -233,25 +233,25 @@ async function release(pathArg, opts = {}) {
233
233
  }
234
234
 
235
235
  if (alreadyGone) {
236
- console.log(`\n ${ok("✓")} Temporary project ${color.bold(tempProjectId)} was already gone from the server` +
236
+ console.log(`\n ${ok("✓")} Temporary project ${color.bold(tempProjectId)} was already gone from the server` +
237
237
  color.dim(" (deleted from the UI, or reaped after sitting idle)."));
238
238
  console.log(repinned.length
239
239
  ? ` ${ok("✓")} Cleaned up ${repinned.length} local registration(s) that still pointed at it. ` +
240
240
  "Niro routes back to the original project automatically."
241
241
  : ` ${ok("✓")} Nothing left to clean up locally.`);
242
242
  } else {
243
- console.log(`\n ${ok("✓")} Temporary project discarded. Niro routes back to the original project automatically.`);
243
+ console.log(`\n ${ok("✓")} Temporary project discarded. Niro routes back to the original project automatically.`);
244
244
  }
245
245
  if (healed.length) {
246
- console.log(color.dim(" Healed old-style pins:"));
246
+ console.log(color.dim(" Healed old-style pins:"));
247
247
  for (const h of healed) console.log(color.dim(` ${h.folder} → ${h.to}`));
248
248
  }
249
249
  if (failed.length) {
250
- console.log(warn(`\n ⚠ ${failed.length} folder(s) could not be restored locally (the temporary project` +
250
+ console.log(warn(`\n ⚠ ${failed.length} folder(s) could not be restored locally (the temporary project` +
251
251
  " is already gone server-side):"));
252
252
  for (const f of failed) console.log(color.dim(` ${f.folder} — ${f.error}`));
253
253
  }
254
- console.log(color.dim(" The original project picks up your merged commits via its normal remote polling."));
254
+ console.log(color.dim(" The original project picks up your merged commits via its normal remote polling."));
255
255
  }
256
256
 
257
257
  module.exports = { release };
@@ -71,9 +71,9 @@ async function remove(opts = {}) {
71
71
  const expected = alias ? `"${alias}" or ${projectId}` : projectId;
72
72
  console.error(`${color.red("✗")} Refusing to delete: --confirm-name must equal the project's name (${expected}).`);
73
73
  if (confirm == null) {
74
- console.error(color.dim(" Pass --confirm-name <name> to confirm this destructive delete."));
74
+ console.error(color.dim(" Pass --confirm-name <name> to confirm this destructive delete."));
75
75
  } else {
76
- console.error(color.dim(` You passed --confirm-name "${confirm}", which does not match.`));
76
+ console.error(color.dim(` You passed --confirm-name "${confirm}", which does not match.`));
77
77
  }
78
78
  process.exitCode = 1;
79
79
  return;
@@ -86,9 +86,9 @@ async function remove(opts = {}) {
86
86
 
87
87
  function printUsage() {
88
88
  console.error("niro remove — remove a project or a repo mapping. Exactly one mode required:");
89
- console.error(" --project delete the whole project (DESTRUCTIVE)");
90
- console.error(" requires --confirm-name <project alias or id>");
91
- console.error(" --unmap-repo <gitId> remove one repo from the project (non-destructive)");
89
+ console.error(" --project delete the whole project (DESTRUCTIVE)");
90
+ console.error(" requires --confirm-name <project alias or id>");
91
+ console.error(" --unmap-repo <gitId> remove one repo from the project (non-destructive)");
92
92
  console.error("Target project: --target <alias|id>, or a .niro-project pin in this folder.");
93
93
  }
94
94