wirl 0.2.0 → 0.2.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.
Files changed (3) hide show
  1. package/README.md +12 -9
  2. package/bin.js +99 -36
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -3,17 +3,16 @@
3
3
  Deploy an internal app somewhere real, privately. One command puts the folder you are in on a URL that only your company can open.
4
4
 
5
5
  ```bash
6
- npm install -g https://app.wirl.dev/cli/wirl.tgz
7
- wirl login # a link and a code; approve in the browser
8
- wirl init my-tool --server
6
+ npx wirl login # a link and a code; approve in the browser
7
+ npx wirl init my-tool --server
9
8
  cd my-tool
10
- wirl dev # runs it here, signed in as a stub user
11
- wirl preview # a private address to look at it before it is live
12
- wirl deploy # builds, scans, deploys; prints the URL
13
- wirl share dana@company.com
9
+ npx wirl dev # runs it here, signed in as a stub user
10
+ npx wirl preview # a private address to look at it before it is live
11
+ npx wirl deploy # builds, scans, deploys; prints the URL
12
+ npx wirl share dana@company.com
14
13
  ```
15
14
 
16
- Install it from wirl itself, as above, and run plain `wirl`. Do not run `npx wirl`: that name on npm belongs to an unrelated older package today, and `npx` would fetch that one.
15
+ `npx` fetches the CLI from npm the first time and needs no install beyond Node 22.13 or later; in a terminal npm first asks "Ok to proceed? (y)": answer y. Elsewhere in this README a command is written `wirl <command>`, for short: run it as `npx wirl <command>`, or run `npm install -g wirl` once and use plain `wirl`. If your npm registry does not carry wirl, or to run exactly the build app.wirl.dev serves, install from app.wirl.dev instead: `npm install -g https://app.wirl.dev/cli/wirl.tgz`, and run plain `wirl`.
17
16
 
18
17
  Sign in with your work Google account and you are in your company's workspace. Anything a colleague deploys there is yours to open, and nobody outside the company can. A personal account gets a space of its own.
19
18
 
@@ -41,11 +40,15 @@ That opens the browser once to sign in. The token an agent gets can deploy, roll
41
40
  ## Everything else
42
41
 
43
42
  ```bash
44
- wirl --help
43
+ npx wirl --help
45
44
  ```
46
45
 
47
46
  Orgs, members, connections, transfer, move, versions, rollback, pull, previews, logs, schedules, run.
48
47
 
48
+ ## Versions
49
+
50
+ The npm package and the control plane are versioned separately. `npx wirl` runs the latest CLI published to npm, and app.wirl.dev runs the build it was last deployed with, so either can be the newer. A CLI newer than the control plane says so where it can tell: `its control plane is older than this copy of wirl` (from `wirl migrations` and `wirl migrations adopt`), or `this control plane is older than sending wirl.json in the body, so it cannot take it` when a `wirl.json` is too large for a header. A CLI older than the control plane sends your whole `wirl.json` and says of a key it does not read that it `is not a key this version of wirl reads, so it is ignored`, though the control plane may read it; it checks `wirl.json` against its own rules before it uploads, so update the CLI (`npm install -g wirl@latest`, or `npx wirl@latest <command>`) when a deploy is refused on your machine for something the docs allow.
51
+
49
52
  ## Requirements
50
53
 
51
54
  Node 22.13 or later. Credentials live in `~/.config/wirl/credentials.json` after `wirl login`, or in `WIRL_TOKEN` for CI and sandboxes.
package/bin.js CHANGED
@@ -3972,20 +3972,21 @@ arrives: pass \`response.body\` straight on. There are no WebSockets. A front en
3972
3972
  \`"static": "dist"\` serves that folder's files at their own paths (\`/\` is its \`index.html\`) and the module
3973
3973
  answers everything else; add \`"spa": true\` for a client-side router. Nothing outside that folder is served.
3974
3974
 
3975
- Preview before you deploy: \`wirl preview\` puts the folder up at its own address with
3976
- its own database (empty at first), and \`wirl fetch <path> --preview <name>\` (or the
3977
- \`wirl_preview_fetch\` tool) shows you what it serves.
3975
+ Preview before you deploy: \`wirl preview\` gives a folder its own address and database (at first only what
3976
+ \`migrations/\` makes). \`wirl fetch <path> --preview <name>\` or \`wirl_preview_fetch\` shows what it answers.
3978
3977
 
3979
3978
  npm packages work: install them in the project and the deploy bundles them in. Nothing is installed on the
3980
3979
  server: native code will not run. Node built-ins work (\`node:crypto\`, \`node:path\`, ...; \`Buffer\` and
3981
3980
  \`process\` are global), but what \`node:fs\` writes is gone by the next request: use \`env.DB\`.
3982
3981
 
3983
3982
  \`env\` holds \`DB\` and nothing else. \`env.DB\` is Cloudflare D1: prepared statements with \`.bind()\`, at most
3984
- 100 bound parameters a statement. \`exec()\` runs one statement per line. Declare a host under \`connections\`
3985
- in \`wirl.json\`, then \`fetch\` the vendor's real URL: wirl attaches the key. An undeclared host is refused; a
3986
- public API needing no key uses \`"kind": "none"\`. Declaring one deploys before anyone approves it;
3987
- the deploy says per connection whether it is usable now or waiting on an admin. A vendor has 60
3988
- seconds to answer, whole, unless it streams: ask a model for a stream.
3983
+ 100 bound parameters a statement. \`exec()\` runs one statement per line, as does each file in \`migrations/\`,
3984
+ which wirl applies in order to a preview and before going live; a failing one fails the deploy. Never edit
3985
+ an applied file. Dropping or renaming a table or column needs \`"migrations": { "destructive": [...] }\`, its
3986
+ own live deploy and a person's yes. Declare a host under \`connections\`, then \`fetch\` the vendor's real URL:
3987
+ wirl attaches the key. An undeclared host is refused; a public API needing no key uses \`"kind": "none"\`.
3988
+ Declaring one deploys before anyone approves it; the deploy says per connection whether it is usable now or
3989
+ waiting on an admin. A vendor has 60 seconds to answer, whole, unless it streams: ask a model for a stream.
3989
3990
 
3990
3991
  Six headers say who is asking: \`x-wirl-user-id\`, \`x-wirl-user-email\`, \`x-wirl-user-name\`,
3991
3992
  \`x-wirl-role\`, \`x-wirl-app-id\`, \`x-wirl-org\`. Read the name with
@@ -3995,10 +3996,9 @@ Schedules POST to \`/__wirl/schedule/<name>\`; a run gets 5 minutes unless that
3995
3996
  says otherwise, and at most 15. A \`public_paths\` path takes requests with no sign-in.
3996
3997
  Work that outlives one request (waiting for a person, trying tomorrow, a long loop) is a flow: see Flows.
3997
3998
 
3998
- A new app defaults to org-wide visibility when its org has a company email domain,
3999
- and to private otherwise, including one made with \`wirl orgs create\`.
4000
- Sharing by email adds one named person; \`wirl visibility private|org\` (or the tool)
4001
- sets the level. Public is only ever a person's click on the app's Access page.
3999
+ A new app defaults to org-wide visibility when its org has a company email domain, and to private
4000
+ otherwise, including one made with \`wirl orgs create\`; \`wirl visibility private|org\` sets the level.
4001
+ Sharing by email adds one named person. Public is only ever a person's click on the app's Access page.
4002
4002
 
4003
4003
  Porting an existing project: route it through the \`fetch\` handler, replace ORM calls with
4004
4004
  \`env.DB.prepare(...).bind(...)\`, turn timers into \`schedules\`, and move keys into \`connections\`. Keep the
@@ -4030,18 +4030,40 @@ early, repeated or late one gets 409, or 404 once the run ends; check \`x-wirl-u
4030
4030
  }
4031
4031
  });
4032
4032
 
4033
+ // packages/core/src/version.ts
4034
+ function wirlVersion() {
4035
+ return BUILT_VERSION ?? WORKSPACE_VERSION;
4036
+ }
4037
+ var WORKSPACE_VERSION, BUILT_VERSION;
4038
+ var init_version = __esm({
4039
+ "packages/core/src/version.ts"() {
4040
+ "use strict";
4041
+ WORKSPACE_VERSION = "0.0.0";
4042
+ BUILT_VERSION = true ? "0.2.1" : null;
4043
+ }
4044
+ });
4045
+
4033
4046
  // packages/core/src/install.ts
4047
+ function fallbackWhen(controlPlane = "this control plane") {
4048
+ return `If your npm registry does not carry wirl, or to run exactly the build ${controlPlane} serves,`;
4049
+ }
4050
+ function namedControlPlane(controlOrigin) {
4051
+ return `the wirl control plane (${controlOrigin.replace(/^https?:\/\//, "").replace(/\/+$/, "")})`;
4052
+ }
4034
4053
  function installFor(controlOrigin) {
4035
- if (NPM_PUBLISHED) return { cli: "wirl", mcp: "@wirl/mcp" };
4036
4054
  const origin = controlOrigin.replace(/\/+$/, "");
4037
- return { cli: `${origin}/cli/wirl.tgz`, mcp: `${origin}/cli/wirl-mcp.tgz` };
4055
+ const tarball = { cli: `${origin}/cli/wirl.tgz`, mcp: `${origin}/cli/wirl-mcp.tgz` };
4056
+ if (NPM_PUBLISHED) return { cli: "wirl", mcp: "@wirl/mcp", tarball };
4057
+ return { ...tarball, tarball };
4038
4058
  }
4039
- var NPM_PUBLISHED, WIRL_BIN;
4059
+ var NPM_PUBLISHED, WIRL_BIN, FALLBACK_WHEN, hasFallback;
4040
4060
  var init_install = __esm({
4041
4061
  "packages/core/src/install.ts"() {
4042
4062
  "use strict";
4043
- NPM_PUBLISHED = false;
4063
+ NPM_PUBLISHED = true;
4044
4064
  WIRL_BIN = NPM_PUBLISHED ? "npx wirl" : "wirl";
4065
+ FALLBACK_WHEN = fallbackWhen();
4066
+ hasFallback = (install) => install.cli !== install.tarball.cli;
4045
4067
  }
4046
4068
  });
4047
4069
 
@@ -4322,6 +4344,7 @@ var init_src = __esm({
4322
4344
  init_retention();
4323
4345
  init_identity();
4324
4346
  init_contract();
4347
+ init_version();
4325
4348
  init_install();
4326
4349
  init_porting();
4327
4350
  init_supplied_bundle();
@@ -6460,7 +6483,7 @@ async function loadCredentials(env = process.env) {
6460
6483
  }
6461
6484
  } catch {
6462
6485
  }
6463
- throw new CliError("Not signed in.", "Run `wirl login` first, or set WIRL_TOKEN.", { code: "not_signed_in" });
6486
+ throw new CliError("Not signed in.", `Run \`${WIRL_BIN} login\` first, or set WIRL_TOKEN.`, { code: "not_signed_in" });
6464
6487
  }
6465
6488
  async function saveCredentials(creds, env = process.env) {
6466
6489
  const path = credentialsPath(env);
@@ -6521,11 +6544,11 @@ async function pollDeviceLogin(apiOrigin, login, options = {}) {
6521
6544
  continue;
6522
6545
  }
6523
6546
  case "expired_token":
6524
- throw new CliError("The sign-in code expired before it was approved.", "Run `wirl login` again.");
6547
+ throw new CliError("The sign-in code expired before it was approved.", `Run \`${WIRL_BIN} login\` again.`);
6525
6548
  default:
6526
6549
  throw new CliError(
6527
6550
  typeof body["error"] === "string" ? `Sign-in failed: ${body["error"]}.` : `Sign-in failed (${response.status}).`,
6528
- "Run `wirl login` again."
6551
+ `Run \`${WIRL_BIN} login\` again.`
6529
6552
  );
6530
6553
  }
6531
6554
  }
@@ -6636,6 +6659,10 @@ var SERVER_TEMPLATE = [
6636
6659
  " *",
6637
6660
  " * Everything real happens here. The page is HTML this module returns. The",
6638
6661
  " * data lives in env.DB, a SQLite database that belongs to this app alone.",
6662
+ " * Its tables are in migrations/0001_init.sql: wirl applies each new file in",
6663
+ " * migrations/ before a version goes live, to a preview's database when the",
6664
+ " * preview is put up, and to the local one when wirl dev starts. Put the",
6665
+ " * next change in migrations/0002_<what>.sql rather than editing that one.",
6639
6666
  " * The person making the request is in headers wirl sets after verifying",
6640
6667
  " * them, which the browser cannot forge. Any rule that matters, such as",
6641
6668
  ' * "you only see your own notes", is a line of code in this file: a check',
@@ -6647,18 +6674,8 @@ var SERVER_TEMPLATE = [
6647
6674
  " * live in .wirl/keys.json beside this file, which git ignores.",
6648
6675
  " */",
6649
6676
  "",
6650
- "// One line: exec runs one statement per line, as D1 does.",
6651
- "const SCHEMA =",
6652
- ' "create table if not exists notes (" +',
6653
- ' " id integer primary key," +',
6654
- ' " user_id text not null," +',
6655
- ' " body text not null," +',
6656
- ` " created_at text not null default (datetime('now'))" +`,
6657
- ' ")";',
6658
- "",
6659
6677
  "export default {",
6660
6678
  " async fetch(request, env) {",
6661
- " await env.DB.exec(SCHEMA);",
6662
6679
  " const url = new URL(request.url);",
6663
6680
  "",
6664
6681
  ` // One line per request. Read it with ${WIRL_BIN} logs.`,
@@ -6741,7 +6758,41 @@ var SERVER_TEMPLATE = [
6741
6758
  "}",
6742
6759
  ""
6743
6760
  ].join("\n");
6761
+ var INIT_MIGRATION = [
6762
+ "-- This app's tables. wirl applies each new file here, in order, before a version goes live.",
6763
+ "-- Make the next change in 0002_<what>.sql; never edit a file that has been deployed.",
6764
+ "create table notes (id integer primary key, user_id text not null, body text not null, created_at text not null default (datetime('now')))",
6765
+ ""
6766
+ ].join("\n");
6767
+ var INIT_MIGRATION_PATH = `${MIGRATIONS_DIR}/${ADOPTED_FILE}`;
6768
+ async function refuseInitMigrations(dir) {
6769
+ for (const entry of await readdir(dir, { withFileTypes: true })) {
6770
+ if (entry.name.toLowerCase() !== MIGRATIONS_DIR) continue;
6771
+ if (entry.isSymbolicLink()) {
6772
+ throw new CliError(`${entry.name}/ is a symbolic link, and wirl writes into no links: make migrations/ a real folder, then run wirl init --server again.`);
6773
+ }
6774
+ if (entry.name !== MIGRATIONS_DIR) {
6775
+ throw new CliError(`${entry.name}/ is spelled differently from migrations/: wirl reads migrations/ in lowercase only. Rename it, then run wirl init --server again.`);
6776
+ }
6777
+ if (!entry.isDirectory()) {
6778
+ throw new CliError(
6779
+ `migrations is a file, and wirl init --server writes ${INIT_MIGRATION_PATH} into a folder of that name. Move it aside, then run wirl init --server again.`
6780
+ );
6781
+ }
6782
+ const names = await readdir(join5(dir, entry.name));
6783
+ if (names.some((name) => name.toLowerCase().endsWith(".sql"))) {
6784
+ throw new CliError(`migrations/ already holds .sql files; wirl init --server writes ${INIT_MIGRATION_PATH} into a folder with none.`);
6785
+ }
6786
+ }
6787
+ }
6744
6788
  var SKILL_PATH = join5(".claude", "skills", "wirl", "SKILL.md");
6789
+ var SKILL_ORIGIN = "https://app.wirl.dev";
6790
+ var SKILL_INSTALL = installFor(SKILL_ORIGIN);
6791
+ var SKILL_RUN = NPM_PUBLISHED ? `Run the CLI as \`${WIRL_BIN} <command>\`, which needs no install beyond Node 22.13 or later (in a terminal npm asks "Ok to proceed?" the first time: answer y); this file writes a command as \`wirl <command>\` for short anywhere but the list below.
6792
+
6793
+ ` : "";
6794
+ var SKILL_FALLBACK = hasFallback(SKILL_INSTALL) ? `- ${fallbackWhen(namedControlPlane(SKILL_ORIGIN))} install the CLI from the control plane instead with \`npm install -g ${SKILL_INSTALL.tarball.cli}\`, run plain \`wirl\` where this file says \`${WIRL_BIN}\`, and name \`${SKILL_INSTALL.tarball.mcp}\` in place of \`${SKILL_INSTALL.mcp}\`.
6795
+ ` : "";
6745
6796
  var SKILL_MARKDOWN = `---
6746
6797
  name: wirl
6747
6798
  description: Deploy and share the internal app in this project with wirl (${WIRL_BIN}). Use when the user wants a tool, dashboard or prototype put somewhere colleagues can open it, shared with a named person, run locally the way it will run deployed, or connected to a third-party API; or wants work that outlives one request, such as waiting for a person to approve something or trying again tomorrow.
@@ -6758,20 +6809,23 @@ Before you build, fetch https://docs.wirl.dev/llms-full.txt: every page of the d
6758
6809
 
6759
6810
  ## Commands
6760
6811
 
6761
- - Connect first, if the agent supports MCP: \`claude mcp add --transport http wirl https://app.wirl.dev/mcp\` opens the browser once to sign in. No HTTP MCP support? \`claude mcp add wirl -- npx -y ${installFor("https://app.wirl.dev").mcp}\` does the same, running locally. Neither available: \`${WIRL_BIN} login\` prints a link and a code; the person approves in the browser. Do this once, when any command says you are not signed in.
6812
+ ${SKILL_RUN}- Connect first, if the agent supports MCP: \`claude mcp add --transport http wirl https://app.wirl.dev/mcp\` opens the browser once to sign in, and needs no install. No HTTP MCP support? \`claude mcp add wirl -- npx -y ${SKILL_INSTALL.mcp}\` does the same, running locally. Neither available: \`${WIRL_BIN} login\` prints a link and a code; the person approves in the browser. Do this once, when any command says you are not signed in.
6762
6813
  - \`${WIRL_BIN} deploy\` \u2014 deploys this directory. Prints the URL and who can open it.
6763
6814
  - \`${WIRL_BIN} share someone@company.com\` \u2014 that person can open it. Add \`--role editor\` so they can deploy too.
6764
6815
  - \`${WIRL_BIN} dev\` \u2014 runs the app here as it will run deployed, signed in as a stub user.
6765
6816
  - \`${WIRL_BIN} pull <app>\` \u2014 fetches a deployed app's source to keep building.
6766
6817
  - \`${WIRL_BIN} logs\` \u2014 what the deployed app printed, per request, newest first; \`--errors\` for failures only, \`--follow\` to keep watching, \`--cursor <n>\` for the page before a "More before this" line. Read this before changing code when something misbehaves.
6818
+ - \`${WIRL_BIN} migrations\` \u2014 what this app's database has applied, read only.
6819
+ - \`${WIRL_BIN} migrations adopt\` \u2014 once, for an app whose tables were made in code: writes \`migrations/0001_init.sql\` from the live database's shape.
6767
6820
  - \`${WIRL_BIN} --help\` \u2014 everything else: orgs, members, connections, move, transfer, rollback, flows and runs.
6768
-
6821
+ ${SKILL_FALLBACK}
6769
6822
  ## Rules
6770
6823
 
6771
6824
  1. **Never ask for or paste an API key.** Declare the connection in \`wirl.json\` and deploy. Needs no key at all (a public API)? Declare it with "kind": "none" and its hosts. If the key is missing, the deploy prints a link to the app's keys page; show the person that link. Locally, keys live in \`.wirl/keys.json\`, which git ignores. To try a vendor call locally without the key, map its connection to a server of your own in \`.wirl/mocks.json\`, like \`{ "anthropic": "http://127.0.0.1:4010" }\`: \`wirl dev\` then sends the calls to that connection's hosts there instead (\`wirl dev --help\` has the rest).
6772
6825
  2. **Call third-party APIs by their real URL** (\`fetch("https://api.hubspot.com/...")\`). wirl attaches the key on the way out, deployed and under \`wirl dev\` alike.
6773
6826
  3. If a command names several organisations and asks which, ask the person; pass \`--org\` or put \`"org"\` in \`wirl.json\`.
6774
6827
  4. To keep a file out of the deploy, list it in a \`.wirlignore\` at the project root (gitignore-style lines; no \`!\` negation).
6828
+ 5. **A file that can lose data waits for a person.** A deploy that would apply a destructive file (one wirl.json's \`"destructive"\` list must name) to the live database stops and names the page where a person approves each one. Show the person those pages, and deploy again once they say they approved. If \`${WIRL_BIN} deploy\` asks you to type a file's name, stop it and show the page instead, or deploy with \`--json\`, which never asks. Never type the name yourself or send the confirmation: that is the person's, and no flag skips it.
6775
6829
 
6776
6830
  ## wirl.json
6777
6831
 
@@ -6822,8 +6876,12 @@ async function scaffold(dir, options) {
6822
6876
  } catch (error) {
6823
6877
  if (error instanceof CliError) throw error;
6824
6878
  }
6879
+ await refuseInitMigrations(dir);
6825
6880
  await writeFile2(target, SERVER_TEMPLATE, "utf8");
6826
6881
  written.push("server.mjs");
6882
+ await mkdir3(join5(dir, MIGRATIONS_DIR), { recursive: true });
6883
+ await writeFile2(join5(dir, MIGRATIONS_DIR, ADOPTED_FILE), INIT_MIGRATION, { encoding: "utf8", flag: "wx" });
6884
+ written.push(INIT_MIGRATION_PATH);
6827
6885
  const pkg = join5(dir, "package.json");
6828
6886
  if (await readFile3(pkg, "utf8").catch(() => null) === null) {
6829
6887
  await writeFile2(pkg, `${JSON.stringify({ name: options.app, private: true, type: "module" }, null, 2)}
@@ -7113,7 +7171,7 @@ async function callApi(creds, path, options = {}) {
7113
7171
  throw new CliError(body.message, void 0, refusal);
7114
7172
  }
7115
7173
  if (response.status === 401 || response.status === 403) {
7116
- throw new CliError("Not signed in, or this token cannot do that.", "Try `wirl login`.", refusal);
7174
+ throw new CliError("Not signed in, or this token cannot do that.", `Try \`${WIRL_BIN} login\`.`, refusal);
7117
7175
  }
7118
7176
  if (response.status === 404) {
7119
7177
  if (body.error !== void 0 && body.error !== "not_found" && body.message) throw new CliError(body.message, void 0, refusal);
@@ -7151,7 +7209,7 @@ async function callApiBytes(creds, path, options = {}) {
7151
7209
  body = {};
7152
7210
  }
7153
7211
  if (response.status === 401 || response.status === 403) {
7154
- throw new CliError("Not signed in, or this token cannot do that.", "Try `wirl login`.");
7212
+ throw new CliError("Not signed in, or this token cannot do that.", `Try \`${WIRL_BIN} login\`.`);
7155
7213
  }
7156
7214
  if (response.status === 404) throw new CliError("Not found, or you do not have access to it.");
7157
7215
  throw new CliError(body.message ?? body.error ?? `Request failed (${response.status}).`);
@@ -7551,7 +7609,7 @@ function deletedAppMessage(org, app, done) {
7551
7609
  return `Deleted ${org}/${app}. Its address has stopped answering${runs}. Its data is kept until ${done.dataKeptUntil.slice(0, 10)}; until then wirl apps restore ${app} --org ${org} brings it back (one that was public comes back only from the Recently deleted list on the dashboard). Its name stays taken.`;
7552
7610
  }
7553
7611
  function deleteNeedsFullScopeMessage(apiOrigin, org, app) {
7554
- return `This sign-in can deploy, but not delete an app: deleting one needs a full-scope sign-in. Sign in with wirl login (with WIRL_TOKEN unset), or delete it on its Settings page: ${apiOrigin}/apps/${seg(org)}/${seg(app)}/settings`;
7612
+ return `This sign-in can deploy, but not delete an app: deleting one needs a full-scope sign-in. Sign in with ${WIRL_BIN} login (with WIRL_TOKEN unset), or delete it on its Settings page: ${apiOrigin}/apps/${seg(org)}/${seg(app)}/settings`;
7555
7613
  }
7556
7614
  var VISIBILITY_LEVELS = ["private", "org", "public"];
7557
7615
  function describeVisibility(level, org) {
@@ -8642,7 +8700,7 @@ var ADOPT_SENTENCES2 = {
8642
8700
  */
8643
8701
  cannotTell: (what) => `wirl did not get the adopt route's answer (${what}), so it cannot tell whether the live database recorded migrations/${ADOPTED_FILE}, and nothing was written here. Run wirl migrations adopt again: while an adopted ${ADOPTED_FILE} is all the live database records, adopting again replaces that record and writes the file.`,
8644
8702
  /** After the call, so the live database records the file; a CLI that reads files otherwise than the control plane says this every time. */
8645
- notAsRecorded: (install) => `The live database now records migrations/${ADOPTED_FILE} as adopted, but the file wirl sent does not match its own record of it, so nothing was written here. Run wirl migrations adopt again: while an adopted ${ADOPTED_FILE} is all the live database records, adopting again replaces that record and writes the file. If it says this again, this copy of wirl reads migration files differently from the control plane: update it (npm install -g ${install}).`,
8703
+ notAsRecorded: (install) => `The live database now records migrations/${ADOPTED_FILE} as adopted, but the file wirl sent does not match its own record of it, so nothing was written here. Run wirl migrations adopt again: while an adopted ${ADOPTED_FILE} is all the live database records, adopting again replaces that record and writes the file. If it says this again, this copy of wirl reads migration files differently from the control plane: update it (npm install -g ${install.cli}${hasFallback(install) ? `, or, to match this control plane exactly, npm install -g ${install.tarball.cli}` : ""}).`,
8646
8704
  unwritten: (code) => `The live database now records migrations/${ADOPTED_FILE} as adopted, but wirl could not write that file here (${code}). Make migrations/ a folder you can write that holds no .sql file, then run wirl migrations adopt again: while an adopted ${ADOPTED_FILE} is all the live database records, adopting again replaces that record and writes the file.`,
8647
8705
  nothingWritten: "Nothing was written to migrations/.",
8648
8706
  /**
@@ -8833,7 +8891,7 @@ async function adoptIntoProject(creds, options) {
8833
8891
  const read = await readMigrations([{ name: ADOPTED_FILE, text: answer.file.text }], []);
8834
8892
  const [file, ...others] = read.ok ? read.files : [];
8835
8893
  if (file === void 0 || others.length > 0 || file.sha256 !== answer.file.sha256 || file.statements.length !== answer.file.statements) {
8836
- throw new CliError(ADOPT_SENTENCES2.notAsRecorded(installFor(creds.apiOrigin).cli));
8894
+ throw new CliError(ADOPT_SENTENCES2.notAsRecorded(installFor(creds.apiOrigin)));
8837
8895
  }
8838
8896
  const path = await writeAdopted(projectDir, answer.file.text);
8839
8897
  const { recordLocalAdoption: recordLocalAdoption2 } = await Promise.resolve().then(() => (init_migrations_local(), migrations_local_exports));
@@ -9917,6 +9975,7 @@ Options
9917
9975
  --app <slug> App. Defaults to wirl.json or the directory name.
9918
9976
  --no-create Fail instead of creating an app that does not exist yet.
9919
9977
  --json Machine-readable output.
9978
+ --version Print the version of wirl you are running, as the first word: wirl --version.
9920
9979
 
9921
9980
  wirl <command> --help for a command's flags and an example.
9922
9981
  `;
@@ -10411,6 +10470,10 @@ async function resolveTarget(creds, args2, dir) {
10411
10470
  var args = parseArgs(process.argv.slice(2).map((token) => token === "-h" ? "--help" : token));
10412
10471
  async function main() {
10413
10472
  const json2 = args.flags.has("json");
10473
+ if (process.argv[2] === "--version" || process.argv[2] === "-v") {
10474
+ out(wirlVersion());
10475
+ return 0;
10476
+ }
10414
10477
  if (args.command === "help") {
10415
10478
  if (!args.flags.has("help")) {
10416
10479
  refuseExtraPositionals("help", args.positional, 1, "one command", { hint: "A command's help lists its subcommands: wirl help <command>." });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wirl",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "Deploy an internal app somewhere real, privately. One command; sharing by email; API keys declared, never pasted.",
5
5
  "keywords": [
6
6
  "deploy",