deployai 0.1.0-beta.2 → 0.1.0-beta.3

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 +10 -7
  2. package/dist/index.js +172 -47
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -41,12 +41,12 @@ Node 20 or newer. No other setup: the CLI talks to https://deployai.co.il.
41
41
  | `deployai init` | Creates `DEPLOYAI.md` (rules for the AI agent) and `.deployai/project.json`. Never edits your code and does not link a project (use `link`). `--force` overwrites `DEPLOYAI.md`. |
42
42
  | `deployai inspect` | Shows framework, package manager, language, database, storage and the number of env files (values are never read). `--json` for machine output. |
43
43
  | `deployai check` | Lists blockers and warnings with file and line. It answers only whether the project can run on DeployAI. More below. |
44
- | `deployai login` | Logs in through the browser (device code). The token is stored in `~/.deployai/config.json`. |
45
- | `deployai logout` | Removes the token from this computer. |
44
+ | `deployai login` | Logs in through the browser (device code). The token lasts 30 days and is stored in `~/.deployai/config.json` with the API address that issued it; it is never sent to another address. |
45
+ | `deployai logout` | Cancels the token on DeployAI and removes it from this computer. |
46
46
  | `deployai whoami` | Shows the logged in user. |
47
- | `deployai link [project]` | Links this folder to a project (slug, id or name). `--create "<name>"` makes a new project. |
47
+ | `deployai link [project]` | Links this folder to a project (slug, id or name). `--create "<name>"` makes a new project. With no name it never links on its own: in a terminal it asks first, an agent gets the exact `link <slug>` command. |
48
48
  | `deployai deploy -m "<message>"` | Runs the check, packs the source and uploads it. Prints the version, the queue position and the preview address. |
49
- | `deployai versions` | Lists versions: number, live or not, status, date, size, message. |
49
+ | `deployai versions` | Lists versions: number, live or not, status ("replaced by a newer upload" for one that never built), date, size, message, and a dashboard link when its SQL waits for approval. |
50
50
  | `deployai rollback v5` | Puts version 5 back live, with no rebuild. |
51
51
  | `deployai promote v5` | Puts version 5 live. |
52
52
  | `deployai status` | Site state, address, live version and queue. |
@@ -78,6 +78,7 @@ Global options:
78
78
  **`deployai deploy --guest`**
79
79
  - No login needed: puts the site live as a temporary site with a claim link, deleted after the days DeployAI sets unless you claim it.
80
80
  - A temporary site may have a small database, but no file storage or environment variables.
81
+ - A folder linked to a site in an account refuses: run `login`, then `deploy`.
81
82
  - For a database sample it prints the site's owner keys once ("shown once, save them now"). They are never stored, so save them right away.
82
83
 
83
84
  **`deployai samples [name] [folder]`**
@@ -111,9 +112,11 @@ The default is `https://deployai.co.il`. The order is: `--api <url>`, then env `
111
112
  The source folder, packed as a `.tar.gz`. The upload is capped at 50 MB.
112
113
 
113
114
  Never uploaded:
114
- - `node_modules`, `.git`, `dist`, `build`, `.next`, `.astro`
115
+ - `node_modules`, `.git`, and tool caches like `.next`, `.astro`, `.svelte-kit`, `.wrangler`
116
+ - `dist`, `build`, `out` at the project root when `package.json` has a build script (DeployAI builds them again). Folders like `assets/build/` are uploaded.
117
+ - `.deployai/`, `DEPLOYAI.md`, `CLAUDE.md`, `AGENTS.md`, `.claude/`, `.cursor/`, `.vscode/` and other dotfiles (except `.well-known/` and build config like `.babelrc`). DeployAI never serves these either, however they arrive.
115
118
  - Any `.env*` file
116
- - Anything listed in `.deployaiignore` (gitignore style)
119
+ - Anything in the root `.gitignore` or in `.deployaiignore` (gitignore style, read last, so `!file` brings a file back)
117
120
  - Files over 10 MB
118
121
 
119
122
  ## Telemetry
@@ -143,4 +146,4 @@ pnpm --filter ./packages/cli start -- check
143
146
  - `@deployai/shared` and `zod` are bundled into `dist/index.js` by tsup, so the published package depends only on `commander`.
144
147
  - The package ships only `dist` and this README (check with `npm pack --dry-run`).
145
148
  - Publish from this folder with `pnpm publish`: `prepublishOnly` builds and runs the tests first.
146
- - Not published yet: it needs the owner's npm account.
149
+ - On npm as `deployai`: run it with `npx deployai@latest <command>`.
package/dist/index.js CHANGED
@@ -20914,16 +20914,19 @@ var DeployResponse = external_exports.object({
20914
20914
  /** Only for a heavy home page (D-420): the files it loads from the site and whether it prefetches links. */
20915
20915
  page: external_exports.object({ files: external_exports.number(), prefetches: external_exports.boolean() }).optional()
20916
20916
  });
20917
+ var VersionStatus = external_exports.enum(["queued", "building", "ready", "failed", "replaced"]);
20918
+ var MigrationStatus = external_exports.enum(["waiting", "queued", "running", "done", "failed"]);
20917
20919
  var Version = external_exports.object({
20918
20920
  id: external_exports.string(),
20919
20921
  number: external_exports.number(),
20920
20922
  message: external_exports.string(),
20921
- status: external_exports.enum(["queued", "building", "ready", "failed"]),
20923
+ status: VersionStatus,
20922
20924
  isLive: external_exports.boolean(),
20923
20925
  createdAt: external_exports.string(),
20924
20926
  sizeBytes: external_exports.number(),
20925
20927
  /** Why the build failed, in words the customer's AI can act on. Null unless status is failed. */
20926
- error: external_exports.string().nullable().optional()
20928
+ error: external_exports.string().nullable().optional(),
20929
+ migrationStatus: MigrationStatus.nullable().optional()
20927
20930
  });
20928
20931
  var VersionsResponse = external_exports.object({ versions: external_exports.array(Version) });
20929
20932
  var EnvSetRequest = external_exports.object({ projectId: external_exports.string(), key: external_exports.string(), value: external_exports.string(), secret: external_exports.boolean().default(true) });
@@ -20936,13 +20939,15 @@ var StatusResponse = external_exports.object({
20936
20939
  project: Project,
20937
20940
  queue: external_exports.object({ position: external_exports.number(), running: external_exports.boolean() }),
20938
20941
  /** The newest version, with the error and the end of the build log when it failed. */
20939
- latest: external_exports.object({ number: external_exports.number(), status: external_exports.enum(["queued", "building", "ready", "failed"]), error: external_exports.string().nullable(), logTail: external_exports.string().nullable() }).nullable().optional()
20942
+ latest: external_exports.object({ number: external_exports.number(), status: VersionStatus, error: external_exports.string().nullable(), logTail: external_exports.string().nullable(), migrationStatus: MigrationStatus.nullable().optional() }).nullable().optional()
20940
20943
  });
20941
20944
  var ApiError = external_exports.object({
20942
20945
  error: external_exports.string(),
20943
20946
  messageHe: external_exports.string().optional(),
20944
20947
  /** Where to buy what the call needs (D-206). */
20945
- buyUrl: external_exports.string().optional()
20948
+ buyUrl: external_exports.string().optional(),
20949
+ /** The dashboard page that resolves the refusal, for example the SQL to approve. */
20950
+ url: external_exports.string().optional()
20946
20951
  });
20947
20952
  var AddonRunStatus = external_exports.enum(["todo", "served", "done", "failed", "skipped"]);
20948
20953
  var AddonItem = external_exports.object({
@@ -21132,14 +21137,14 @@ var ARCH_NODES = [
21132
21137
  { id: "email-sending", kind: "external", zone: "services", label: "CF Email Sending", summary: "\u05E9\u05DC\u05D9\u05D7\u05EA \u05DE\u05D9\u05D9\u05DC\u05D9\u05DD \u05D3\u05E8\u05DA Cloudflare Email Sending. \u05D1\u05DC\u05D9 \u05D8\u05D5\u05E7\u05DF \u05D4\u05DE\u05D9\u05D9\u05DC \u05DE\u05D5\u05D3\u05E4\u05E1 \u05D1\u05DC\u05D5\u05D2.", summaryEn: "Sends email through Cloudflare Email Sending. Without a token, the email is printed to the log.", code: ["apps/web/src/server/email.ts"], portability: "Emails" },
21133
21138
  { id: "oauth", kind: "external", zone: "services", label: "Google / GitHub OAuth", summary: "\u05D4\u05EA\u05D7\u05D1\u05E8\u05D5\u05EA \u05E2\u05DD Google \u05D0\u05D5 GitHub \u05D3\u05E8\u05DA Better Auth.", summaryEn: "Log in with Google or GitHub through Better Auth.", code: ["apps/web/src/server/auth.ts"], env: ["GOOGLE_CLIENT_ID", "GOOGLE_CLIENT_SECRET", "GITHUB_CLIENT_ID", "GITHUB_CLIENT_SECRET"], decisions: ["D-020"] },
21134
21139
  { id: "payments", kind: "external", zone: "services", label: "Payment provider (Cardcom)", summary: "\u05E7\u05D0\u05E8\u05D3\u05E7\u05D5\u05DD \u05DB\u05D1\u05E8\u05D9\u05E8\u05EA \u05DE\u05D7\u05D3\u05DC, \u05D5\u05E0\u05D1\u05D7\u05E8 \u05D1\u05D4\u05D2\u05D3\u05E8\u05D5\u05EA \u05D4\u05D0\u05D3\u05DE\u05D9\u05DF (\u05D0\u05D5 \u05DE\u05D3\u05D5\u05DE\u05D4 \u05DC\u05D1\u05D3\u05D9\u05E7\u05D5\u05EA). \u05D4\u05DB\u05E8\u05D8\u05D9\u05E1 \u05DE\u05D5\u05E7\u05DC\u05D3 \u05D1\u05D3\u05E3 \u05D4\u05DE\u05D0\u05D5\u05D1\u05D8\u05D7 \u05E9\u05DC \u05E7\u05D0\u05E8\u05D3\u05E7\u05D5\u05DD \u05D5\u05E0\u05E9\u05DE\u05E8 \u05DB\u05D8\u05D5\u05E7\u05DF \u05DE\u05D5\u05E6\u05E4\u05DF, \u05DB\u05DC \u05D7\u05D9\u05D5\u05D1 \u05E2\u05DD \u05DE\u05E4\u05EA\u05D7 \u05D0\u05D9\u05D3\u05DE\u05E4\u05D5\u05D8\u05E0\u05D8\u05D9\u05D5\u05EA, \u05D5\u05E7\u05D0\u05E8\u05D3\u05E7\u05D5\u05DD \u05DE\u05E4\u05D9\u05E7\u05D4 \u05D7\u05E9\u05D1\u05D5\u05E0\u05D9\u05EA \u05DE\u05E1 \u05DC\u05DB\u05DC \u05D7\u05D9\u05D5\u05D1 \u05D5\u05D7\u05E9\u05D1\u05D5\u05E0\u05D9\u05EA \u05D6\u05D9\u05DB\u05D5\u05D9 \u05DC\u05DB\u05DC \u05D4\u05D7\u05D6\u05E8.", summaryEn: "Cardcom by default, chosen in admin settings (or simulated for tests). The card is typed on the secure Cardcom page and saved as a sealed token, every charge carries an idempotency key, and Cardcom issues a tax invoice per charge and a credit invoice per refund.", code: ["apps/web/src/server/billing/provider.ts", "apps/web/src/server/billing/cardcom.ts", "apps/web/src/server/billing/card-pages.ts", "apps/web/src/app/api/billing/card"], env: ["BILLING_PROVIDER", "BILLING_MOCK_FAIL", "CARDCOM_TERMINAL", "CARDCOM_API_NAME", "CARDCOM_API_PASSWORD", "CARDCOM_INVOICES", "CARDCOM_CSS_URL", "CARDCOM_TOKEN_KEY", "CARDCOM_DEPARTMENT_ID"], portability: "Payments", decisions: ["D-175", "D-292"] },
21135
- { id: "registrar", kind: "external", zone: "services", label: "Domain registrar (Cloudflare Registrar)", summary: "\u05E7\u05E0\u05D9\u05D9\u05EA \u05D3\u05D5\u05DE\u05D9\u05D9\u05E0\u05D9\u05DD \u05DC\u05D0\u05EA\u05E8: \u05D7\u05D9\u05E4\u05D5\u05E9 \u05D5\u05DE\u05D7\u05D9\u05E8 \u05D1\u05D3\u05D5\u05DC\u05E8\u05D9\u05DD, \u05E8\u05D9\u05E9\u05D5\u05DD \u05D0\u05D7\u05D3 \u05DC\u05DB\u05DC \u05D4\u05D6\u05DE\u05E0\u05D4 \u05D1\u05D8\u05D5\u05E7\u05DF \u05E0\u05E4\u05E8\u05D3, \u05D5\u05D7\u05D9\u05D3\u05D5\u05E9 \u05E9\u05E0\u05EA\u05D9. \u05D3\u05D5\u05DE\u05D9\u05D9\u05E0\u05D9\u05DD \u05D9\u05E9\u05E8\u05D0\u05DC\u05D9\u05D9\u05DD \u05E0\u05E8\u05E9\u05DE\u05D9\u05DD \u05D9\u05D3\u05E0\u05D9\u05EA \u05DE\u05EA\u05D5\u05E8 \u05D4\u05E6\u05D5\u05D5\u05EA. \u05DC\u05D3\u05D5\u05DE\u05D9\u05D9\u05DF \u05E9\u05E0\u05E7\u05E0\u05D4: \u05D3\u05D5\u05D0\u05E8 \u05D3\u05E8\u05DA Cloudflare Email Routing.", summaryEn: "Buying domains for a site: search and price in dollars, one registration per order with its own token, yearly renewal. Israeli domains are registered by hand from the team queue. For a bought domain: email through Cloudflare Email Routing.", code: ["apps/web/src/server/registrar.ts", "apps/web/src/server/domain-sales.ts", "apps/web/src/server/domain-email.ts", "apps/web/src/server/billing/services.ts"], env: ["CF_REGISTRAR_TOKEN"], decisions: ["D-299", "D-300", "D-301"] },
21136
- { id: "mcp", kind: "external", zone: "services", label: "MCP for AI tools (/api/mcp)", summary: "ChatGPT \u05D5-Claude \u05DE\u05EA\u05D7\u05D1\u05E8\u05D9\u05DD \u05D1\u05DC\u05D9 \u05D8\u05E8\u05DE\u05D9\u05E0\u05DC: OAuth \u05E2\u05DD \u05D0\u05D9\u05E9\u05D5\u05E8 \u05D1\u05D3\u05E4\u05D3\u05E4\u05DF, \u05D5\u05D0\u05D6 \u05DB\u05DC\u05D9\u05DD \u05DC\u05E8\u05E9\u05D9\u05DE\u05EA \u05D4\u05D0\u05EA\u05E8\u05D9\u05DD, \u05D1\u05D3\u05D9\u05E7\u05D4, \u05D4\u05E2\u05DC\u05D0\u05EA \u05D2\u05E8\u05E1\u05D4 \u05DC\u05EA\u05E6\u05D5\u05D2\u05D4 \u05DE\u05E7\u05D3\u05D9\u05DE\u05D4, \u05D5\u05D4\u05E2\u05DC\u05D0\u05D4 \u05DC\u05D0\u05D5\u05D5\u05D9\u05E8 \u05E8\u05E7 \u05E2\u05DD \u05D4\u05E8\u05E9\u05D0\u05D4 \u05D5\u05D0\u05D9\u05E9\u05D5\u05E8.", summaryEn: "ChatGPT and Claude connect without a terminal: OAuth with a browser consent, then tools to list sites, check, upload a version to a preview, and go live only with the permission and a yes.", code: ["apps/web/src/app/api/mcp/route.ts", "apps/web/src/server/mcp-tools.ts", "apps/web/src/server/auth.ts"], decisions: ["D-302"] },
21140
+ { id: "registrar", kind: "external", zone: "services", label: "Domain registrars (Cloudflare, LiveDNS)", summary: "\u05E7\u05E0\u05D9\u05D9\u05EA \u05D3\u05D5\u05DE\u05D9\u05D9\u05E0\u05D9\u05DD \u05DC\u05D0\u05EA\u05E8: \u05DB\u05DC \u05E9\u05DD \u05D4\u05D5\u05DC\u05DA \u05DC\u05E8\u05E9\u05DD \u05D4\u05D6\u05D5\u05DC \u05D1\u05D9\u05D5\u05EA\u05E8 \u05E9\u05E4\u05D5\u05E2\u05DC (Cloudflare \u05D1\u05D3\u05D5\u05DC\u05E8\u05D9\u05DD, LiveDNS \u05DC\u05D9\u05E9\u05E8\u05D0\u05DC\u05D9\u05D9\u05DD \u05DE\u05E7\u05E8\u05D3\u05D9\u05D8 \u05D1\u05E9\u05E7\u05DC\u05D9\u05DD, \u05D0\u05D5 \u05D9\u05D3\u05E0\u05D9 \u05DE\u05EA\u05D5\u05E8 \u05D4\u05E6\u05D5\u05D5\u05EA), \u05E8\u05D9\u05E9\u05D5\u05DD \u05D0\u05D7\u05D3 \u05DC\u05DB\u05DC \u05D4\u05D6\u05DE\u05E0\u05D4, \u05D9\u05D5\u05DE\u05DF \u05E7\u05E8\u05D9\u05D0\u05D5\u05EA \u05DC\u05DB\u05DC \u05D4\u05D6\u05DE\u05E0\u05D4, \u05D5\u05D7\u05D9\u05D3\u05D5\u05E9 \u05E9\u05E0\u05EA\u05D9 \u05D0\u05E6\u05DC \u05D0\u05D5\u05EA\u05D5 \u05E8\u05E9\u05DD. \u05DC\u05D3\u05D5\u05DE\u05D9\u05D9\u05DF \u05E9\u05E0\u05E7\u05E0\u05D4: \u05D3\u05D5\u05D0\u05E8 \u05D3\u05E8\u05DA Cloudflare Email Routing.", summaryEn: "Buying domains for a site: each name goes to the cheapest registrar that is on (Cloudflare in dollars, LiveDNS for Israeli names from shekel credit, or by hand from the team queue), one registration per order, a call log per order, yearly renewal at the same registrar. For a bought domain: email through Cloudflare Email Routing.", code: ["apps/web/src/server/registrar.ts", "apps/web/src/server/registrar-livedns.ts", "apps/web/src/server/registrar-log.ts", "apps/web/src/server/domain-sales.ts", "apps/web/src/server/domain-email.ts", "apps/web/src/server/billing/services.ts"], env: ["CF_REGISTRAR_TOKEN", "LIVEDNS_USER", "LIVEDNS_PASSWORD"], decisions: ["D-299", "D-300", "D-301", "D-458"] },
21141
+ { id: "mcp", kind: "external", zone: "services", label: "MCP for AI tools (/api/mcp)", summary: "ChatGPT \u05D5-Claude \u05DE\u05EA\u05D7\u05D1\u05E8\u05D9\u05DD \u05D1\u05DC\u05D9 \u05D8\u05E8\u05DE\u05D9\u05E0\u05DC: OAuth \u05E2\u05DD \u05D0\u05D9\u05E9\u05D5\u05E8 \u05D1\u05D3\u05E4\u05D3\u05E4\u05DF, \u05D5\u05D0\u05D6 \u05DB\u05DC\u05D9\u05DD \u05DC\u05E8\u05E9\u05D9\u05DE\u05EA \u05D4\u05D0\u05EA\u05E8\u05D9\u05DD, \u05D1\u05D3\u05D9\u05E7\u05D4, \u05D4\u05E2\u05DC\u05D0\u05EA \u05D2\u05E8\u05E1\u05D4 \u05DC\u05EA\u05E6\u05D5\u05D2\u05D4 \u05DE\u05E7\u05D3\u05D9\u05DE\u05D4, \u05D4\u05E8\u05E6\u05EA \u05EA\u05D5\u05E1\u05E4\u05D9\u05DD \u05E9\u05D4\u05D0\u05EA\u05E8 \u05E7\u05E0\u05D4 (\u05D4\u05E7\u05E0\u05D9\u05D9\u05D4 \u05E8\u05E7 \u05D1\u05D0\u05EA\u05E8), \u05D5\u05D4\u05E2\u05DC\u05D0\u05D4 \u05DC\u05D0\u05D5\u05D5\u05D9\u05E8 \u05E8\u05E7 \u05E2\u05DD \u05D4\u05E8\u05E9\u05D0\u05D4 \u05D5\u05D0\u05D9\u05E9\u05D5\u05E8.", summaryEn: "ChatGPT and Claude connect without a terminal: OAuth with a browser consent, then tools to list sites, check, upload a version to a preview, run add-ons the site owns (buying stays on the site), and go live only with the permission and a yes.", code: ["apps/web/src/app/api/mcp/route.ts", "apps/web/src/server/mcp-tools.ts", "apps/web/src/server/auth.ts"], decisions: ["D-302"] },
21137
21142
  { id: "npm", kind: "external", zone: "services", label: "npm registry", summary: "\u05DE\u05E4\u05D9\u05E5 \u05D0\u05EA \u05D4\u05BECLI, \u05D5\u05DE\u05DE\u05E0\u05D5 \u05D4\u05D1\u05E0\u05D9\u05D9\u05D4 \u05DE\u05EA\u05E7\u05D9\u05E0\u05D4 \u05EA\u05DC\u05D5\u05D9\u05D5\u05EA.", summaryEn: "Distributes the CLI, and the build installs dependencies from it." },
21138
21143
  { id: "pagespeed", kind: "external", zone: "services", label: "Google PageSpeed Insights", summary: "\u05D1\u05D3\u05D9\u05E7\u05D5\u05EA \u05D4\u05DE\u05D4\u05D9\u05E8\u05D5\u05EA, \u05D4\u05E0\u05D2\u05D9\u05E9\u05D5\u05EA \u05D5\u05D4-SEO \u05D4\u05D7\u05D9\u05E0\u05DE\u05D9\u05D5\u05EA: Lighthouse \u05D1\u05DE\u05E2\u05D1\u05D3\u05D4 \u05E9\u05DC \u05D2\u05D5\u05D2\u05DC (\u05DB\u05D5\u05DC\u05DC \u05E1\u05D5\u05DB\u05E0\u05D9 AI) \u05D5\u05E0\u05EA\u05D5\u05E0\u05D9 \u05DE\u05D1\u05E7\u05E8\u05D9\u05DD \u05D0\u05DE\u05D9\u05EA\u05D9\u05D9\u05DD.", summaryEn: "The free speed, accessibility and SEO tests: Lighthouse run at Google (AI agents included) plus real visitor data.", env: ["PAGESPEED_API_KEY"], decisions: ["D-199", "D-222"] },
21139
21144
  { id: "turnstile", kind: "external", zone: "services", label: "CF Turnstile", summary: '\u05D1\u05D3\u05D9\u05E7\u05EA "\u05D4\u05D0\u05DD \u05D0\u05EA\u05DD \u05D1\u05E0\u05D9 \u05D0\u05D3\u05DD" \u05D1\u05D8\u05E4\u05E1\u05D9 \u05D4\u05DB\u05DC\u05D9\u05DD \u05D4\u05D7\u05D9\u05E0\u05DE\u05D9\u05D9\u05DD, \u05D1\u05DC\u05D9 \u05E4\u05D0\u05D6\u05DC\u05D9\u05DD \u05DC\u05E8\u05D5\u05D1 \u05D4\u05DE\u05D1\u05E7\u05E8\u05D9\u05DD.', summaryEn: 'The "are you human" check on the free tool forms, with no puzzle for most visitors.', env: ["TURNSTILE_SITE_KEY", "TURNSTILE_SECRET"], decisions: ["D-199"] },
21140
21145
  // Cloudflare
21141
21146
  { id: "tunnel", kind: "cf-resource", zone: "cloudflare", label: "Cloudflare Tunnel", summary: "\u05D4\u05D9\u05D5\u05DD \u05D4\u05D0\u05E4\u05DC\u05D9\u05E7\u05E6\u05D9\u05D4 \u05E8\u05E6\u05D4 \u05E2\u05DC \u05D4\u05DE\u05D7\u05E9\u05D1 \u05D4\u05D6\u05D4 \u05D5\u05E0\u05D7\u05E9\u05E4\u05EA \u05D3\u05E8\u05DA dev.deployai.co.il (pnpm tunnel, \u05D0\u05D5 \u05D4\u05DB\u05DC \u05D9\u05D7\u05D3 \u05D1\u05BEpnpm dev:public).", summaryEn: "Today the app runs on this computer and is exposed through dev.deployai.co.il (pnpm tunnel, or everything at once with pnpm dev:public).", code: ["scripts/dev-public.ts"], env: ["TUNNEL_ORIGIN_CERT"], decisions: ["D-089", "D-148"] },
21142
- { id: "cfapi", kind: "external", zone: "cloudflare", label: "Cloudflare API", summary: "\u05DB\u05DC \u05E4\u05E2\u05D5\u05DC\u05D4 \u05DE\u05D5\u05DC Cloudflare \u05E2\u05D5\u05D1\u05E8\u05EA \u05D1\u05BEREST API \u05E2\u05DD \u05D3\u05DC\u05D9 \u05D8\u05D5\u05E7\u05E0\u05D9\u05DD \u05DE\u05E9\u05D5\u05EA\u05E3. \u05D1\u05DC\u05D9 Wrangler.", summaryEn: "Every Cloudflare action goes through the REST API with a shared token bucket. No Wrangler.", code: ["apps/web/src/server/cloudflare.ts", "scripts/cf-setup.ts"], env: ["CF_API_TOKEN"], docs: ["docs/10-cloudflare-reference.md"], decisions: ["D-007", "D-031", "D-058"] },
21147
+ { id: "cfapi", kind: "external", zone: "cloudflare", label: "Cloudflare API", summary: "\u05DB\u05DC \u05E4\u05E2\u05D5\u05DC\u05D4 \u05DE\u05D5\u05DC Cloudflare \u05E2\u05D5\u05D1\u05E8\u05EA \u05D1\u05BEREST API \u05E2\u05DD \u05D3\u05DC\u05D9 \u05D8\u05D5\u05E7\u05E0\u05D9\u05DD \u05DE\u05E9\u05D5\u05EA\u05E3. \u05D1\u05DC\u05D9 Wrangler.", summaryEn: "Every Cloudflare action goes through the REST API with a shared token bucket. No Wrangler.", code: ["apps/web/src/server/cloudflare.ts", "scripts/cf-setup.ts", "apps/web/src/server/admin-geo.ts"], env: ["CF_API_TOKEN", "ADMIN_GEO_MANAGE"], docs: ["docs/10-cloudflare-reference.md"], decisions: ["D-007", "D-031", "D-058", "D-455"] },
21143
21148
  { id: "demos", kind: "worker", zone: "cloudflare", label: "deployai-demos", summary: "\u05D4\u05D3\u05DE\u05D5\u05D0\u05D9\u05DD \u05D1\u05DB\u05EA\u05D5\u05D1\u05D5\u05EA \u05DB\u05DE\u05D5 shop.deploy-net.com, \u05E2\u05DC \u05DB\u05DC \u05D3\u05D5\u05DE\u05D9\u05D9\u05DF \u05D0\u05EA\u05E8\u05D9\u05DD: \u05DE\u05D5\u05E9\u05DA \u05D0\u05EA /demo/<key> \u05DE\u05D4\u05D0\u05EA\u05E8 \u05E9\u05DC\u05E0\u05D5 \u05D5\u05DE\u05E1\u05D9\u05E8 \u05D0\u05EA \u05D4\u05E7\u05D9\u05D3\u05D5\u05DE\u05EA, noindex.", summaryEn: "The demos on hosts like shop.deploy-net.com, on every sites domain: fetches /demo/<key> from our site and strips the prefix, noindex.", code: ["workers/demos/src", "scripts/deploy-demos.ts", "packages/shared/src/demos.ts"], env: ["CHROME_PATH", "SHOTS_BASE"], docs: ["workers/demos/README.md"], decisions: ["D-112", "D-293"] },
21144
21149
  { id: "tools-worker", kind: "worker", zone: "cloudflare", label: "deployai-tools", summary: "\u05DE\u05D5\u05E8\u05D9\u05D3 \u05D0\u05EA \u05D4\u05E2\u05DE\u05D5\u05D3\u05D9\u05DD \u05D5\u05D4\u05EA\u05DE\u05D5\u05E0\u05D5\u05EA \u05E9\u05D4\u05DB\u05DC\u05D9\u05DD \u05D4\u05D7\u05D9\u05E0\u05DE\u05D9\u05D9\u05DD \u05D1\u05D5\u05D3\u05E7\u05D9\u05DD, \u05DB\u05D3\u05D9 \u05E9\u05DB\u05EA\u05D5\u05D1\u05EA \u05E9\u05DE\u05D1\u05E7\u05E8 \u05D4\u05E7\u05DC\u05D9\u05D3 \u05DC\u05D0 \u05EA\u05D9\u05D8\u05E2\u05DF \u05DE\u05D4\u05E9\u05E8\u05EA \u05E9\u05DC\u05E0\u05D5. \u05E8\u05E7 \u05E9\u05DE\u05D5\u05EA \u05E6\u05D9\u05D1\u05D5\u05E8\u05D9\u05D9\u05DD, \u05DB\u05DC \u05EA\u05E9\u05D5\u05D1\u05EA DNS \u05D5\u05DB\u05DC \u05D4\u05E4\u05E0\u05D9\u05D4 \u05E0\u05D1\u05D3\u05E7\u05D5\u05EA.", summaryEn: "Downloads the pages and images the free tools test, so an address a visitor typed is never fetched from our server. Public names only; every DNS answer and redirect is checked.", code: ["workers/tools/src", "scripts/deploy-tools.ts", "packages/shared/src/safe-url.ts"], env: ["TOOLS_FETCH_SECRET"], decisions: ["D-199"] },
21145
21150
  { id: "uptime-worker", kind: "worker", zone: "cloudflare", label: "deployai-uptime", summary: "\u05D1\u05D5\u05D3\u05E7 \u05DE\u05D1\u05D7\u05D5\u05E5 \u05DB\u05DC 2 \u05D3\u05E7\u05D5\u05EA \u05E9-/api/health \u05E2\u05D5\u05E0\u05D4, \u05DB\u05D9 \u05E9\u05E8\u05EA \u05E9\u05E0\u05E4\u05DC \u05DC\u05D0 \u05D9\u05DB\u05D5\u05DC \u05DC\u05D3\u05D5\u05D5\u05D7 \u05E2\u05DC \u05E2\u05E6\u05DE\u05D5. \u05E9\u05D5\u05DE\u05E8 \u05D0\u05EA \u05D4\u05EA\u05D5\u05E6\u05D0\u05D4 \u05D1-KV, \u05D5\u05E9\u05EA\u05D9 \u05E0\u05E4\u05D9\u05DC\u05D5\u05EA \u05D1\u05E8\u05E6\u05E3 \u05E9\u05D5\u05DC\u05D7\u05D5\u05EA \u05DE\u05D9\u05D9\u05DC \u05DC\u05E6\u05D5\u05D5\u05EA.", summaryEn: "Checks from outside every 2 minutes that /api/health answers, because a server that is down cannot report itself. Keeps the result in KV; two failures in a row email the team.", code: ["workers/uptime/src", "scripts/deploy-uptime.ts", "apps/web/src/app/api/health/route.ts"], env: ["UPTIME_ALERT_TO", "UPTIME_ALERT_TOKEN"], decisions: ["D-331"] },
@@ -21263,8 +21268,8 @@ var ARCH_FLOWS = [
21263
21268
 
21264
21269
  // ../shared/src/cost-model.ts
21265
21270
  var DEFAULT_FLEET = {
21266
- mix: { basic: 0.65, business: 0.3, freelancer: 0.05 },
21267
- sitesPerAccount: { free: 1, basic: 1, business: 2, freelancer: 6 },
21271
+ mix: { basic: 0.65, business: 0.3, agency: 0.05 },
21272
+ sitesPerAccount: { free: 1, basic: 1, business: 2, agency: 6 },
21268
21273
  freeProfile: "quiet",
21269
21274
  paidProfile: "typical",
21270
21275
  ownDomainShare: 0.6,
@@ -21352,7 +21357,7 @@ var CLI_MAP_NODES = [
21352
21357
  n("cmd-init", "cli", "init", "\u05D9\u05D5\u05E6\u05E8 DEPLOYAI.md \u05D5 .deployai/project.json.", "Creates DEPLOYAI.md and .deployai/project.json."),
21353
21358
  n("cmd-inspect", "cli", "inspect", "\u05DE\u05D4 \u05D6\u05D5\u05D4\u05D4 \u05D1\u05E4\u05E8\u05D5\u05D9\u05E7\u05D8: \u05DE\u05E1\u05D2\u05E8\u05EA, \u05E9\u05E4\u05D4, \u05DE\u05E1\u05D3 \u05E0\u05EA\u05D5\u05E0\u05D9\u05DD.", "What the project is: framework, language, database."),
21354
21359
  n("cmd-login", "cli", "login", "\u05DB\u05E0\u05D9\u05E1\u05D4 \u05D1\u05D3\u05E4\u05D3\u05E4\u05DF, \u05D1\u05E2\u05DC \u05D4\u05D0\u05EA\u05E8 \u05DE\u05D0\u05E9\u05E8.", "Login in the browser, the owner approves."),
21355
- n("cmd-logout", "cli", "logout", "\u05D9\u05E6\u05D9\u05D0\u05D4 \u05DE\u05D4\u05D7\u05E9\u05D1\u05D5\u05DF \u05D1\u05DE\u05D7\u05E9\u05D1 \u05D4\u05D6\u05D4.", "Logs out on this computer."),
21360
+ n("cmd-logout", "cli", "logout", "\u05DE\u05D1\u05D8\u05DC \u05D0\u05EA \u05D4\u05D8\u05D5\u05E7\u05DF \u05D5\u05D9\u05D5\u05E6\u05D0 \u05DE\u05D4\u05D7\u05E9\u05D1\u05D5\u05DF \u05D1\u05DE\u05D7\u05E9\u05D1 \u05D4\u05D6\u05D4.", "Cancels the token and logs out on this computer."),
21356
21361
  n("cmd-whoami", "cli", "whoami", "\u05E2\u05DD \u05D0\u05D9\u05D6\u05D4 \u05D7\u05E9\u05D1\u05D5\u05DF \u05DE\u05D7\u05D5\u05D1\u05E8\u05D9\u05DD.", "Which account is logged in."),
21357
21362
  n("cmd-link", "cli", "link [site]", "\u05DE\u05D7\u05D1\u05E8 \u05D0\u05EA \u05D4\u05EA\u05D9\u05E7\u05D9\u05D9\u05D4 \u05DC\u05D0\u05EA\u05E8, \u05D0\u05D5 \u05D9\u05D5\u05E6\u05E8 \u05D0\u05EA\u05E8 \u05E2\u05DD --create.", "Links the folder to a site, or makes one with --create."),
21358
21363
  n("cmd-deploy", "cli", "deploy -m", "\u05D1\u05D5\u05D3\u05E7, \u05D0\u05D5\u05E8\u05D6 \u05D5\u05DE\u05E2\u05DC\u05D4 \u05D2\u05E8\u05E1\u05D4 \u05DC\u05EA\u05E6\u05D5\u05D2\u05D4 \u05DE\u05E7\u05D3\u05D9\u05DE\u05D4.", "Checks, packs and uploads a version to a preview."),
@@ -21363,7 +21368,7 @@ var CLI_MAP_NODES = [
21363
21368
  n("cmd-promote", "cli", "promote v<N>", "\u05DE\u05E2\u05DC\u05D4 \u05DC\u05D0\u05D5\u05D5\u05D9\u05E8 \u05D2\u05E8\u05E1\u05D4 \u05E9\u05D0\u05D5\u05E9\u05E8\u05D4.", "Puts an approved version live."),
21364
21369
  n("cmd-versions", "cli", "versions", "\u05E8\u05E9\u05D9\u05DE\u05EA \u05D4\u05D2\u05E8\u05E1\u05D0\u05D5\u05EA \u05E9\u05DC \u05D4\u05D0\u05EA\u05E8.", "The site's versions."),
21365
21370
  n("cmd-rollback", "cli", "rollback v<N>", "\u05D7\u05D5\u05D6\u05E8 \u05DC\u05D2\u05E8\u05E1\u05D4 \u05E7\u05D5\u05D3\u05DE\u05EA.", "Goes back to an earlier version."),
21366
- n("cmd-addons", "cli", "addons / addon next", "\u05D4\u05EA\u05D5\u05E1\u05E4\u05D9\u05DD \u05E9\u05DC \u05D4\u05D0\u05EA\u05E8, \u05D5\u05D4\u05E9\u05DC\u05D1 \u05D4\u05D1\u05D0 \u05E9\u05DC \u05EA\u05D5\u05E1\u05E3 \u05E9\u05E0\u05E7\u05E0\u05D4.", "The site's add-ons, and the next step of one it bought."),
21371
+ n("cmd-addons", "cli", "addons / addon next / addon buy", "\u05D4\u05EA\u05D5\u05E1\u05E4\u05D9\u05DD \u05E9\u05DC \u05D4\u05D0\u05EA\u05E8, \u05D4\u05E9\u05DC\u05D1 \u05D4\u05D1\u05D0 \u05E9\u05DC \u05EA\u05D5\u05E1\u05E3 \u05E9\u05E0\u05E7\u05E0\u05D4, \u05D5\u05E7\u05D9\u05E9\u05D5\u05E8 \u05E7\u05E0\u05D9\u05D9\u05D4 \u05DC\u05D1\u05E2\u05DC \u05D4\u05D0\u05EA\u05E8.", "The site's add-ons, the next step of one it bought, and a buy link for the owner."),
21367
21372
  n("cmd-addon-run", "cli", "addon run / done", "\u05EA\u05D5\u05E1\u05E3 \u05D0\u05D7\u05D3 \u05DC\u05E4\u05D9 \u05E9\u05DD, \u05D5\u05D3\u05D9\u05D5\u05D5\u05D7 \u05E9\u05D4\u05E9\u05DC\u05D1 \u05D4\u05E1\u05EA\u05D9\u05D9\u05DD.", "One add-on by name, and the report that a step is done."),
21368
21373
  n("cmd-env", "cli", "env list / set / remove", "\u05DE\u05E9\u05EA\u05E0\u05D9 \u05D4\u05E1\u05D1\u05D9\u05D1\u05D4 \u05E9\u05DC \u05D4\u05D0\u05EA\u05E8. \u05E1\u05D5\u05D3 \u05DB\u05D1\u05E8\u05D9\u05E8\u05EA \u05DE\u05D7\u05D3\u05DC, \u05D5\u05DC\u05D0 \u05DE\u05D5\u05E6\u05D2 \u05E9\u05D5\u05D1.", "The site's environment variables. A secret by default, never shown again."),
21369
21374
  n("cmd-speed", "cli", "speed <url>", "\u05D1\u05D3\u05D9\u05E7\u05EA \u05DE\u05D4\u05D9\u05E8\u05D5\u05EA \u05E9\u05DC \u05DB\u05EA\u05D5\u05D1\u05EA \u05D7\u05D9\u05D4.", "A speed test of a live address."),
@@ -21460,6 +21465,31 @@ function findSecretLine(text, re) {
21460
21465
  }
21461
21466
  return null;
21462
21467
  }
21468
+ var PRIVATE_DIRS = /* @__PURE__ */ new Set([".deployai", ".claude", ".cursor", ".vscode", ".idea", ".git", ".github", ".husky"]);
21469
+ var PRIVATE_FILES = /* @__PURE__ */ new Set(["DEPLOYAI.md", "CLAUDE.md", "AGENTS.md", ".cursorrules", ".windsurfrules"]);
21470
+ var SERVED_DOT_DIR = ".well-known";
21471
+ var BUILD_DOTFILES = /* @__PURE__ */ new Set([".babelrc", ".babelrc.json", ".browserslistrc", ".postcssrc", ".postcssrc.json", ".postcssrc.js", ".postcssrc.cjs", ".postcssrc.mjs", ".swcrc", ".nvmrc", ".node-version", ".assetsignore"]);
21472
+ var segments = (path) => path.replace(/^\/+/, "").split("/").filter(Boolean);
21473
+ function privateProjectFile(path) {
21474
+ const parts = segments(path);
21475
+ const base = parts[parts.length - 1] ?? "";
21476
+ return PRIVATE_FILES.has(base) || parts.slice(0, -1).some((p) => PRIVATE_DIRS.has(p)) || PRIVATE_DIRS.has(base);
21477
+ }
21478
+ function dotPath(path) {
21479
+ const parts = segments(path);
21480
+ if (parts[0] === SERVED_DOT_DIR) parts.shift();
21481
+ return parts.some((p) => p.startsWith("."));
21482
+ }
21483
+ function neverUploadedFile(rel) {
21484
+ if (privateProjectFile(rel)) return true;
21485
+ const parts = segments(rel);
21486
+ if (parts.length === 1 && BUILD_DOTFILES.has(parts[0])) return false;
21487
+ return dotPath(rel);
21488
+ }
21489
+
21490
+ // ../shared/src/domain-sales.ts
21491
+ var IL_TLDS = ["co.il", "org.il", "net.il"];
21492
+ var LIVEDNS_TLDS = [...IL_TLDS, "com", "net", "org"];
21463
21493
 
21464
21494
  // ../shared/src/env-scopes.ts
21465
21495
  var ENV_KEYS = {
@@ -21471,6 +21501,9 @@ var ENV_KEYS = {
21471
21501
  TUNNEL_ORIGIN_CERT: { scope: "local", section: "app", about: "The cloudflared login certificate pnpm dev:public checks. Default ~/.cloudflared/cert.pem." },
21472
21502
  CF_API_TOKEN: { scope: "own", section: "cloudflare", required: ["server"], about: "Cloudflare token (pnpm cf:token-link): sites, KV, D1, R2, routes, custom hostnames and our emails. Each machine its own. Without it everything Cloudflare is a dry run." },
21473
21503
  CF_REGISTRAR_TOKEN: { scope: "server", section: "cloudflare", about: "Buys real domains (D-300): a Cloudflare token with Registrar edit only. Server only." },
21504
+ LIVEDNS_USER: { scope: "server", section: "cloudflare", about: "Buys real Israeli domains at LiveDNS (D-458): the API user of our LiveDNS partner account. Server only: LiveDNS refuses some networks, and it spends prepaid credit." },
21505
+ LIVEDNS_PASSWORD: { scope: "server", section: "cloudflare", about: "Its password, sent in every LiveDNS call; never logged. Server only." },
21506
+ ADMIN_GEO_MANAGE: { scope: "server", section: "cloudflare", about: "1 lets the superadmin change the admin-geo rule on Admin, Server, Security (temporary, D-455); CF_API_TOKEN needs Zone WAF Edit. Off or unset: the card only shows it. The rule stays in Cloudflare either way." },
21474
21507
  DISPATCH_RATE_LIMIT_GUEST: { scope: "same", section: "cloudflare", about: "pnpm dispatch:deploy: requests per IP per period on a temporary site (D-087, D-238)." },
21475
21508
  DISPATCH_RATE_LIMIT_FREE: { scope: "same", section: "cloudflare", about: "The same on a site on a free plan." },
21476
21509
  DISPATCH_RATE_LIMIT_PAID: { scope: "same", section: "cloudflare", about: "The same on a site on a paid plan." },
@@ -21628,6 +21661,7 @@ function loadConfig() {
21628
21661
  };
21629
21662
  if (typeof raw.apiUrl === "string") cfg.apiUrl = raw.apiUrl;
21630
21663
  if (typeof raw.token === "string") cfg.token = raw.token;
21664
+ if (typeof raw.tokenApiUrl === "string") cfg.tokenApiUrl = raw.tokenApiUrl;
21631
21665
  if (typeof raw.email === "string") cfg.email = raw.email;
21632
21666
  if (raw.guestClaims && typeof raw.guestClaims === "object") {
21633
21667
  const links = Object.fromEntries(Object.entries(raw.guestClaims).filter(([, v]) => typeof v === "string"));
@@ -21871,10 +21905,18 @@ var M = {
21871
21905
  startExpired: "These instructions expired (they last 7 days). Copy them again from the DeployAI site.",
21872
21906
  startUsedUp: "These instructions were already used 5 times. Copy them again from the DeployAI site.",
21873
21907
  loginExpired: "The login code expired. Run: npx deployai@latest login",
21874
- loggedOut: "Logged out. The token was removed from this computer.",
21908
+ loggedOut: "Logged out. The token was canceled and removed from this computer.",
21875
21909
  protocol: "Unexpected answer from DeployAI. Run the latest CLI: npx deployai@latest",
21876
21910
  guestClaimFirst: "This folder holds a temporary site, and you are logged in. Claim the site to your account first: {url}",
21877
21911
  guestClaimFirstNoLink: "This folder holds a temporary site, and you are logged in. Claim it to your account first with the claim link deploy --guest printed.",
21912
+ guestAccountSite: "This folder belongs to a site in an account, so it cannot go up as a temporary site. Run: npx deployai@latest login, then: npx deployai@latest deploy",
21913
+ sqlWaitingLine: "Database changes (SQL) wait for your approval before this version goes live: {url}",
21914
+ sqlWaiting: "This version has database changes (SQL) that have not run yet. Approve them in the dashboard first, then promote again: {url}",
21915
+ tokenOtherApi: "The login on this computer is for {saved}, not {url}. Run: npx deployai@latest login --api {url}, or drop --api and DEPLOYAI_API_URL.",
21916
+ linkConfirm: "Link this folder to {name} ({address})? (y/N) ",
21917
+ linkOnlyOne: "You have one site: {name} ({address}). To link it, or a new site, run:",
21918
+ linkCancelled: "Not linked. Nothing changed.",
21919
+ loggedOutLocalOnly: "Logged out on this computer. DeployAI could not be reached, so the token stays valid until it expires (30 days).",
21878
21920
  savedIn: "Saved in .deployai/project.json",
21879
21921
  createdProject: "Created the site {name}.",
21880
21922
  noProjects: "No sites yet.",
@@ -21885,7 +21927,7 @@ var M = {
21885
21927
  runLink: " Run: npx deployai@latest link",
21886
21928
  nextSteps: "Next steps",
21887
21929
  readme: "Read README.md in the folder for what each file does.",
21888
- states: { online: "live", building: "building", ready: "ready for review", failed: "failed", paused: "paused", off: "off", queued: "waiting", deleted: "deleted", new: "new" }
21930
+ states: { online: "live", building: "building", ready: "ready for review", failed: "failed", paused: "paused", off: "off", queued: "waiting", replaced: "replaced by a newer upload", deleted: "deleted", new: "new" }
21889
21931
  },
21890
21932
  he: {
21891
21933
  startTitle: "DeployAI start",
@@ -22028,10 +22070,18 @@ var M = {
22028
22070
  startExpired: "\u05E4\u05D2 \u05D4\u05EA\u05D5\u05E7\u05E3 \u05E9\u05DC \u05D4\u05D4\u05D5\u05E8\u05D0\u05D5\u05EA \u05D4\u05D0\u05DC\u05D4 (\u05D4\u05DF \u05EA\u05E7\u05E4\u05D5\u05EA 7 \u05D9\u05DE\u05D9\u05DD). \u05D4\u05E2\u05EA\u05D9\u05E7\u05D5 \u05D0\u05D5\u05EA\u05DF \u05E9\u05D5\u05D1 \u05DE\u05D4\u05D0\u05EA\u05E8 \u05E9\u05DC DeployAI.",
22029
22071
  startUsedUp: "\u05DB\u05D1\u05E8 \u05D4\u05E9\u05EA\u05DE\u05E9\u05D5 \u05D1\u05D4\u05D5\u05E8\u05D0\u05D5\u05EA \u05D4\u05D0\u05DC\u05D4 5 \u05E4\u05E2\u05DE\u05D9\u05DD. \u05D4\u05E2\u05EA\u05D9\u05E7\u05D5 \u05D0\u05D5\u05EA\u05DF \u05E9\u05D5\u05D1 \u05DE\u05D4\u05D0\u05EA\u05E8 \u05E9\u05DC DeployAI.",
22030
22072
  loginExpired: "\u05E7\u05D5\u05D3 \u05D4\u05DB\u05E0\u05D9\u05E1\u05D4 \u05E4\u05D2. \u05D4\u05E8\u05D9\u05E6\u05D5: npx deployai@latest login",
22031
- loggedOut: "\u05D4\u05EA\u05E0\u05EA\u05E7\u05EA\u05DD. \u05D4\u05D8\u05D5\u05E7\u05DF \u05E0\u05DE\u05D7\u05E7 \u05DE\u05D4\u05DE\u05D7\u05E9\u05D1 \u05D4\u05D6\u05D4.",
22073
+ loggedOut: "\u05D4\u05EA\u05E0\u05EA\u05E7\u05EA\u05DD. \u05D4\u05D8\u05D5\u05E7\u05DF \u05D1\u05D5\u05D8\u05DC \u05D5\u05E0\u05DE\u05D7\u05E7 \u05DE\u05D4\u05DE\u05D7\u05E9\u05D1 \u05D4\u05D6\u05D4.",
22032
22074
  protocol: "\u05EA\u05E9\u05D5\u05D1\u05D4 \u05DC\u05D0 \u05E6\u05E4\u05D5\u05D9\u05D4 \u05DE DeployAI. \u05D4\u05E8\u05D9\u05E6\u05D5 \u05D0\u05EA \u05D4\u05D2\u05E8\u05E1\u05D4 \u05D4\u05D0\u05D7\u05E8\u05D5\u05E0\u05D4: npx deployai@latest",
22033
22075
  guestClaimFirst: "\u05D1\u05EA\u05D9\u05E7\u05D9\u05D9\u05D4 \u05D4\u05D6\u05D5 \u05D9\u05E9 \u05D0\u05EA\u05E8 \u05D6\u05DE\u05E0\u05D9, \u05D5\u05D0\u05EA\u05DD \u05DE\u05D7\u05D5\u05D1\u05E8\u05D9\u05DD \u05DC\u05D7\u05E9\u05D1\u05D5\u05DF. \u05E7\u05D5\u05D3\u05DD \u05E9\u05DE\u05E8\u05D5 \u05D0\u05EA \u05D4\u05D0\u05EA\u05E8 \u05D1\u05D7\u05E9\u05D1\u05D5\u05DF: {url}",
22034
22076
  guestClaimFirstNoLink: "\u05D1\u05EA\u05D9\u05E7\u05D9\u05D9\u05D4 \u05D4\u05D6\u05D5 \u05D9\u05E9 \u05D0\u05EA\u05E8 \u05D6\u05DE\u05E0\u05D9, \u05D5\u05D0\u05EA\u05DD \u05DE\u05D7\u05D5\u05D1\u05E8\u05D9\u05DD \u05DC\u05D7\u05E9\u05D1\u05D5\u05DF. \u05E7\u05D5\u05D3\u05DD \u05E9\u05DE\u05E8\u05D5 \u05D0\u05D5\u05EA\u05D5 \u05D1\u05D7\u05E9\u05D1\u05D5\u05DF \u05E2\u05DD \u05E7\u05D9\u05E9\u05D5\u05E8 \u05D4\u05E9\u05DE\u05D9\u05E8\u05D4 \u05E9 deploy --guest \u05D4\u05D3\u05E4\u05D9\u05E1.",
22077
+ guestAccountSite: "\u05D4\u05EA\u05D9\u05E7\u05D9\u05D9\u05D4 \u05D4\u05D6\u05D5 \u05E9\u05D9\u05D9\u05DB\u05EA \u05DC\u05D0\u05EA\u05E8 \u05D1\u05D7\u05E9\u05D1\u05D5\u05DF, \u05D5\u05DC\u05DB\u05DF \u05D4\u05D9\u05D0 \u05DC\u05D0 \u05D9\u05DB\u05D5\u05DC\u05D4 \u05DC\u05E2\u05DC\u05D5\u05EA \u05DB\u05D0\u05EA\u05E8 \u05D6\u05DE\u05E0\u05D9. \u05D4\u05E8\u05D9\u05E6\u05D5: npx deployai@latest login, \u05D5\u05D0\u05D6: npx deployai@latest deploy",
22078
+ sqlWaitingLine: "\u05E9\u05D9\u05E0\u05D5\u05D9\u05D9\u05DD \u05D1\u05DE\u05E1\u05D3 \u05D4\u05E0\u05EA\u05D5\u05E0\u05D9\u05DD (SQL) \u05DE\u05D7\u05DB\u05D9\u05DD \u05DC\u05D0\u05D9\u05E9\u05D5\u05E8 \u05E9\u05DC\u05DB\u05DD \u05DC\u05E4\u05E0\u05D9 \u05E9\u05D4\u05D2\u05E8\u05E1\u05D4 \u05D4\u05D6\u05D5 \u05E2\u05D5\u05DC\u05D4 \u05DC\u05D0\u05D5\u05D5\u05D9\u05E8: {url}",
22079
+ sqlWaiting: "\u05D1\u05D2\u05E8\u05E1\u05D4 \u05D4\u05D6\u05D5 \u05D9\u05E9 \u05E9\u05D9\u05E0\u05D5\u05D9\u05D9\u05DD \u05D1\u05DE\u05E1\u05D3 \u05D4\u05E0\u05EA\u05D5\u05E0\u05D9\u05DD (SQL) \u05E9\u05E2\u05D5\u05D3 \u05DC\u05D0 \u05E8\u05E6\u05D5. \u05D0\u05E9\u05E8\u05D5 \u05D0\u05D5\u05EA\u05DD \u05E7\u05D5\u05D3\u05DD \u05D1\u05DC\u05D5\u05D7 \u05D4\u05D1\u05E7\u05E8\u05D4, \u05D5\u05D0\u05D6 \u05D4\u05E2\u05DC\u05D5 \u05DC\u05D0\u05D5\u05D5\u05D9\u05E8 \u05E9\u05D5\u05D1: {url}",
22080
+ tokenOtherApi: "\u05D4\u05DB\u05E0\u05D9\u05E1\u05D4 \u05D1\u05DE\u05D7\u05E9\u05D1 \u05D4\u05D6\u05D4 \u05D4\u05D9\u05D0 \u05DC {saved}, \u05DC\u05D0 \u05DC {url}. \u05D4\u05E8\u05D9\u05E6\u05D5: npx deployai@latest login --api {url}, \u05D0\u05D5 \u05D4\u05D5\u05E8\u05D9\u05D3\u05D5 \u05D0\u05EA --api \u05D5\u05D0\u05EA DEPLOYAI_API_URL.",
22081
+ linkConfirm: "\u05DC\u05D7\u05D1\u05E8 \u05D0\u05EA \u05D4\u05EA\u05D9\u05E7\u05D9\u05D9\u05D4 \u05D4\u05D6\u05D5 \u05DC {name} ({address})? (y/N) ",
22082
+ linkOnlyOne: "\u05D9\u05E9 \u05DC\u05DB\u05DD \u05D0\u05EA\u05E8 \u05D0\u05D7\u05D3: {name} ({address}). \u05DB\u05D3\u05D9 \u05DC\u05D7\u05D1\u05E8 \u05D0\u05D5\u05EA\u05D5, \u05D0\u05D5 \u05D0\u05EA\u05E8 \u05D7\u05D3\u05E9, \u05D4\u05E8\u05D9\u05E6\u05D5:",
22083
+ linkCancelled: "\u05DC\u05D0 \u05D7\u05D5\u05D1\u05E8. \u05E9\u05D5\u05DD \u05D3\u05D1\u05E8 \u05DC\u05D0 \u05D4\u05E9\u05EA\u05E0\u05D4.",
22084
+ loggedOutLocalOnly: "\u05D4\u05EA\u05E0\u05EA\u05E7\u05EA\u05DD \u05D1\u05DE\u05D7\u05E9\u05D1 \u05D4\u05D6\u05D4. \u05DC\u05D0 \u05D4\u05E6\u05DC\u05D7\u05E0\u05D5 \u05DC\u05D4\u05D2\u05D9\u05E2 \u05DC DeployAI, \u05D0\u05D6 \u05D4\u05D8\u05D5\u05E7\u05DF \u05EA\u05E7\u05E3 \u05E2\u05D3 \u05E9\u05D9\u05E4\u05D5\u05D2 (30 \u05D9\u05D5\u05DD).",
22035
22085
  savedIn: "\u05E0\u05E9\u05DE\u05E8 \u05D1 .deployai/project.json",
22036
22086
  createdProject: "\u05E0\u05D5\u05E6\u05E8 \u05D4\u05D0\u05EA\u05E8 {name}.",
22037
22087
  noProjects: "\u05D0\u05D9\u05DF \u05E2\u05D3\u05D9\u05D9\u05DF \u05D0\u05EA\u05E8\u05D9\u05DD.",
@@ -22042,7 +22092,7 @@ var M = {
22042
22092
  runLink: " \u05D4\u05E8\u05D9\u05E6\u05D5: npx deployai@latest link",
22043
22093
  nextSteps: "\u05D4\u05E9\u05DC\u05D1\u05D9\u05DD \u05D4\u05D1\u05D0\u05D9\u05DD",
22044
22094
  readme: "\u05D1 README.md \u05E9\u05D1\u05EA\u05D9\u05E7\u05D9\u05D9\u05D4 \u05DB\u05EA\u05D5\u05D1 \u05DE\u05D4 \u05DB\u05DC \u05E7\u05D5\u05D1\u05E5 \u05E2\u05D5\u05E9\u05D4.",
22045
- states: { online: "\u05D1\u05D0\u05D5\u05D5\u05D9\u05E8", building: "\u05D1\u05D1\u05E0\u05D9\u05D9\u05D4", ready: "\u05DE\u05D7\u05DB\u05D4 \u05DC\u05D0\u05D9\u05E9\u05D5\u05E8", failed: "\u05E0\u05DB\u05E9\u05DC", paused: "\u05DE\u05D5\u05E9\u05D4\u05D4", off: "\u05DB\u05D1\u05D5\u05D9", queued: "\u05D1\u05EA\u05D5\u05E8", deleted: "\u05E0\u05DE\u05D7\u05E7", new: "\u05D7\u05D3\u05E9" }
22095
+ states: { online: "\u05D1\u05D0\u05D5\u05D5\u05D9\u05E8", building: "\u05D1\u05D1\u05E0\u05D9\u05D9\u05D4", ready: "\u05DE\u05D7\u05DB\u05D4 \u05DC\u05D0\u05D9\u05E9\u05D5\u05E8", failed: "\u05E0\u05DB\u05E9\u05DC", paused: "\u05DE\u05D5\u05E9\u05D4\u05D4", off: "\u05DB\u05D1\u05D5\u05D9", queued: "\u05D1\u05EA\u05D5\u05E8", replaced: "\u05D4\u05D5\u05D7\u05DC\u05E4\u05D4 \u05D1\u05D4\u05E2\u05DC\u05D0\u05D4 \u05D7\u05D3\u05E9\u05D4 \u05D9\u05D5\u05EA\u05E8", deleted: "\u05E0\u05DE\u05D7\u05E7", new: "\u05D7\u05D3\u05E9" }
22046
22096
  }
22047
22097
  };
22048
22098
  function stateName(state, root) {
@@ -22055,7 +22105,7 @@ function messages(root) {
22055
22105
  }
22056
22106
 
22057
22107
  // src/version.ts
22058
- var CLI_VERSION = "0.1.0-beta.2";
22108
+ var CLI_VERSION = "0.1.0-beta.3";
22059
22109
 
22060
22110
  // src/api.ts
22061
22111
  function apiContext(apiFlag) {
@@ -22067,6 +22117,8 @@ function userAgent(cfg) {
22067
22117
  }
22068
22118
  function requireToken(ctx) {
22069
22119
  if (!ctx.config.token) throw new CliError(messages(process.cwd())("notLoggedIn"), 1, "auth");
22120
+ const saved = ctx.config.tokenApiUrl?.replace(/\/+$/, "");
22121
+ if (saved && saved !== ctx.apiUrl.replace(/\/+$/, "")) throw new CliError(messages(process.cwd())("tokenOtherApi", { saved, url: ctx.apiUrl }), 1, "auth");
22070
22122
  return ctx.config.token;
22071
22123
  }
22072
22124
  async function apiRequest(ctx, path, schema, opts = {}) {
@@ -22111,6 +22163,9 @@ async function apiRequest(ctx, path, schema, opts = {}) {
22111
22163
  if (parsed2.success && parsed2.data.error === "not_entitled") {
22112
22164
  throw new CliError(t("notEntitled", { url: parsed2.data.buyUrl ?? ctx.apiUrl }), 1, "not_entitled");
22113
22165
  }
22166
+ if (parsed2.success && parsed2.data.error === "sql_waiting") {
22167
+ throw new CliError(t("sqlWaiting", { url: parsed2.data.url ?? `${ctx.apiUrl}/dashboard` }), 1, "sql_waiting");
22168
+ }
22114
22169
  const known = parsed2.success ? knownError(parsed2.data.error, data?.max, t, { ...data, billingUrl: `${ctx.apiUrl}/billing` }) : null;
22115
22170
  if (known) throw new CliError(known, 1, parsed2.success ? parsed2.data.error : "api");
22116
22171
  const msg = parsed2.success ? parsed2.data.error : `HTTP ${res.status}`;
@@ -22279,22 +22334,20 @@ async function sendTelemetryEvent(ctx, event) {
22279
22334
  }
22280
22335
 
22281
22336
  // src/commands/init.ts
22282
- import { existsSync as existsSync4, writeFileSync as writeFileSync3 } from "fs";
22337
+ import { existsSync as existsSync5, writeFileSync as writeFileSync3 } from "fs";
22283
22338
  import { join as join5 } from "path";
22284
22339
 
22285
22340
  // src/analyze.ts
22286
22341
  import { execFileSync } from "child_process";
22287
- import { existsSync as existsSync3, readFileSync as readFileSync4, readdirSync as readdirSync2 } from "fs";
22342
+ import { existsSync as existsSync4, readFileSync as readFileSync4, readdirSync as readdirSync2 } from "fs";
22288
22343
  import { join as join4 } from "path";
22289
22344
 
22290
22345
  // src/files.ts
22291
- import { readdirSync, readFileSync as readFileSync3, lstatSync } from "fs";
22346
+ import { existsSync as existsSync3, readdirSync, readFileSync as readFileSync3, lstatSync } from "fs";
22292
22347
  import { join as join3 } from "path";
22293
22348
  var SKIP_DIRS = /* @__PURE__ */ new Set([
22294
22349
  "node_modules",
22295
22350
  ".git",
22296
- "dist",
22297
- "build",
22298
22351
  ".next",
22299
22352
  ".astro",
22300
22353
  ".svelte-kit",
@@ -22302,9 +22355,19 @@ var SKIP_DIRS = /* @__PURE__ */ new Set([
22302
22355
  ".vercel",
22303
22356
  ".wrangler",
22304
22357
  ".turbo",
22305
- ".cache",
22306
- "coverage"
22358
+ ".cache"
22307
22359
  ]);
22360
+ var ROOT_BUILD_DIRS = /* @__PURE__ */ new Set(["dist", "build", "out", "coverage"]);
22361
+ function rootBuilds(root) {
22362
+ const file2 = join3(root, "package.json");
22363
+ if (!existsSync3(file2)) return false;
22364
+ try {
22365
+ const pkg = JSON.parse(readFileSync3(file2, "utf8"));
22366
+ return typeof pkg?.scripts?.build === "string";
22367
+ } catch {
22368
+ return true;
22369
+ }
22370
+ }
22308
22371
  var MAX_FILE_BYTES = 10 * 1024 * 1024;
22309
22372
  var MAX_TOTAL_BYTES = 50 * 1024 * 1024;
22310
22373
  function isEnvFile(name) {
@@ -22375,10 +22438,19 @@ function loadIgnore(root) {
22375
22438
  return [];
22376
22439
  }
22377
22440
  }
22441
+ function loadUploadIgnore(root) {
22442
+ let git = [];
22443
+ try {
22444
+ git = parseIgnore(readFileSync3(join3(root, ".gitignore"), "utf8"));
22445
+ } catch {
22446
+ }
22447
+ return [...git, ...loadIgnore(root)];
22448
+ }
22378
22449
  function walkFiles(root, opts = {}) {
22379
22450
  const result = [];
22380
22451
  const maxFiles = opts.maxFiles ?? 2e4;
22381
22452
  const rules = opts.ignore ?? [];
22453
+ const skipRootBuild = opts.skipRootBuild ?? rootBuilds(root);
22382
22454
  const visit2 = (dirAbs, dirRel) => {
22383
22455
  let names;
22384
22456
  try {
@@ -22399,6 +22471,7 @@ function walkFiles(root, opts = {}) {
22399
22471
  if (st.isSymbolicLink()) continue;
22400
22472
  if (st.isDirectory()) {
22401
22473
  if (SKIP_DIRS.has(name)) continue;
22474
+ if (!dirRel && skipRootBuild && ROOT_BUILD_DIRS.has(name)) continue;
22402
22475
  if (isIgnored(rules, rel, true)) continue;
22403
22476
  visit2(abs, rel);
22404
22477
  } else if (st.isFile()) {
@@ -22412,12 +22485,12 @@ function walkFiles(root, opts = {}) {
22412
22485
  return result;
22413
22486
  }
22414
22487
  function planUpload(root) {
22415
- const all = walkFiles(root, { ignore: loadIgnore(root), skipEnv: true, maxFiles: 1e5 });
22488
+ const all = walkFiles(root, { ignore: loadUploadIgnore(root), skipEnv: true, maxFiles: 1e5 });
22416
22489
  const files = [];
22417
22490
  const skippedLarge = [];
22418
22491
  let totalBytes = 0;
22419
22492
  for (const f of all) {
22420
- if (f.rel === ".deployai/last-check.json" || f.rel === ".deployai/brief.md") continue;
22493
+ if (neverUploadedFile(f.rel)) continue;
22421
22494
  if (f.size > MAX_FILE_BYTES) {
22422
22495
  skippedLarge.push(f);
22423
22496
  continue;
@@ -22506,10 +22579,10 @@ function depLine(pkgText, dep) {
22506
22579
  return l ?? void 0;
22507
22580
  }
22508
22581
  function detectPackageManager(root, pkg) {
22509
- if (existsSync3(join4(root, "pnpm-lock.yaml"))) return "pnpm";
22510
- if (existsSync3(join4(root, "bun.lockb")) || existsSync3(join4(root, "bun.lock"))) return "bun";
22511
- if (existsSync3(join4(root, "yarn.lock"))) return "yarn";
22512
- if (existsSync3(join4(root, "package-lock.json"))) return "npm";
22582
+ if (existsSync4(join4(root, "pnpm-lock.yaml"))) return "pnpm";
22583
+ if (existsSync4(join4(root, "bun.lockb")) || existsSync4(join4(root, "bun.lock"))) return "bun";
22584
+ if (existsSync4(join4(root, "yarn.lock"))) return "yarn";
22585
+ if (existsSync4(join4(root, "package-lock.json"))) return "npm";
22513
22586
  const pm = pkg?.packageManager?.split("@")[0];
22514
22587
  if (pm === "pnpm" || pm === "yarn" || pm === "bun" || pm === "npm") return pm;
22515
22588
  return pkg ? "npm" : "none";
@@ -22537,7 +22610,7 @@ function detectFramework2(root, pkg, files, sources) {
22537
22610
  }
22538
22611
  function detectLanguage(root, pkg, files) {
22539
22612
  const deps = { ...pkg?.devDependencies ?? {}, ...pkg?.dependencies ?? {} };
22540
- if (existsSync3(join4(root, "tsconfig.json")) || deps.typescript) return "TypeScript";
22613
+ if (existsSync4(join4(root, "tsconfig.json")) || deps.typescript) return "TypeScript";
22541
22614
  if (files.some((f) => /\.(ts|tsx|mts|cts)$/.test(f.rel) && !f.rel.endsWith(".d.ts"))) return "TypeScript";
22542
22615
  if (files.some((f) => /\.(js|jsx|mjs|cjs)$/.test(f.rel)) || pkg) return "JavaScript";
22543
22616
  return "HTML";
@@ -22554,7 +22627,7 @@ function isBuildTimeFile(rel) {
22554
22627
  return /\.config\.(js|ts|mjs|cjs|mts|cts)$/.test(base) || /^(scripts|tools|tests?|__tests__|e2e|cypress|prisma\/seed)\//.test(rel) || /\/(__tests__|__mocks__)\//.test(rel) || /\.(test|spec|stories)\.[a-z]+$/.test(base) || base.endsWith(".d.ts") || /^seed\.(js|ts|mjs)$/.test(base);
22555
22628
  }
22556
22629
  function trackedEnvFiles(root) {
22557
- if (!existsSync3(join4(root, ".git"))) return [];
22630
+ if (!existsSync4(join4(root, ".git"))) return [];
22558
22631
  try {
22559
22632
  const out2 = execFileSync("git", ["ls-files", "-z"], { cwd: root, encoding: "utf8", timeout: 5e3, stdio: ["ignore", "pipe", "ignore"] });
22560
22633
  return out2.split("\0").filter((p) => {
@@ -22725,7 +22798,7 @@ function analyzeProject(root) {
22725
22798
  }
22726
22799
  if (!databases.some((d) => d.kind === "d1")) {
22727
22800
  for (const name of ["wrangler.toml", "wrangler.json", "wrangler.jsonc"]) {
22728
- if (!existsSync3(join4(root, name))) continue;
22801
+ if (!existsSync4(join4(root, name))) continue;
22729
22802
  const text = readFileSync4(join4(root, name), "utf8");
22730
22803
  const at = text.search(/d1_databases/);
22731
22804
  if (at < 0) continue;
@@ -22902,7 +22975,7 @@ function runInit(root, opts = {}) {
22902
22975
  const created = [];
22903
22976
  const kept = [];
22904
22977
  const md = join5(root, "DEPLOYAI.md");
22905
- if (existsSync4(md) && !opts.force) {
22978
+ if (existsSync5(md) && !opts.force) {
22906
22979
  kept.push("DEPLOYAI.md");
22907
22980
  } else {
22908
22981
  writeFileSync3(md, DEPLOYAI_MD);
@@ -23204,7 +23277,7 @@ async function loginCommand(ctx, opts = {}) {
23204
23277
  });
23205
23278
  if (poll.status === "approved") {
23206
23279
  if (!poll.token) throw new CliError(messages()("loginNoToken"), 1, "protocol");
23207
- updateConfig({ token: poll.token, email: poll.email, apiUrl: ctx.apiUrl });
23280
+ updateConfig({ token: poll.token, email: poll.email, apiUrl: ctx.apiUrl, tokenApiUrl: ctx.apiUrl });
23208
23281
  out();
23209
23282
  out(poll.email ? t("loggedInAs", { email: poll.email }) : t("loggedIn"));
23210
23283
  out(t("nextStep"));
@@ -23215,9 +23288,18 @@ async function loginCommand(ctx, opts = {}) {
23215
23288
  }
23216
23289
  throw new CliError(t("loginExpired"), 1, "auth_expired");
23217
23290
  }
23218
- function logoutCommand() {
23219
- updateConfig({ token: void 0, email: void 0 });
23220
- out(messages()("loggedOut"));
23291
+ async function logoutCommand(ctx) {
23292
+ let revoked = true;
23293
+ if (ctx.config.token) {
23294
+ const apiUrl = (ctx.config.tokenApiUrl ?? ctx.apiUrl).replace(/\/+$/, "");
23295
+ try {
23296
+ await apiRequest({ apiUrl, config: ctx.config }, "/api/cli/logout", null, { method: "POST", body: {}, timeoutMs: 1e4 });
23297
+ } catch (e2) {
23298
+ revoked = e2 instanceof CliError && e2.category === "auth";
23299
+ }
23300
+ }
23301
+ updateConfig({ token: void 0, tokenApiUrl: void 0, email: void 0 });
23302
+ out(messages()(revoked ? "loggedOut" : "loggedOutLocalOnly"));
23221
23303
  }
23222
23304
  async function whoamiCommand(ctx) {
23223
23305
  const me = await apiRequest(ctx, "/api/cli/me", MeResponse);
@@ -23306,8 +23388,21 @@ async function linkCommand(root, ctx, target, opts) {
23306
23388
  return;
23307
23389
  }
23308
23390
  if (projects.length === 1) {
23309
- save(root, projects[0]);
23310
- return;
23391
+ const p = projects[0];
23392
+ const address = p.url ?? p.slug;
23393
+ if (process.stdin.isTTY && process.stdout.isTTY) {
23394
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
23395
+ let answer;
23396
+ try {
23397
+ answer = (await rl.question(t("linkConfirm", { name: p.name, address }))).trim().toLowerCase();
23398
+ } finally {
23399
+ rl.close();
23400
+ }
23401
+ if (answer === "y" || answer === "yes") return save(root, p);
23402
+ out(t("linkCancelled"));
23403
+ return;
23404
+ }
23405
+ throw new CliError([t("linkOnlyOne", { name: p.name, address }), ` npx deployai@latest link ${p.slug}`, ' npx deployai@latest link --create "<name>"'].join("\n"), 1, "usage");
23311
23406
  }
23312
23407
  const current = readProject(root)?.projectId;
23313
23408
  out(t("projects"));
@@ -23487,6 +23582,7 @@ async function deployCommand(root, ctx, opts) {
23487
23582
  if (!ctx.config.token) {
23488
23583
  const guestToken = project?.projectId ? ctx.config.guestTokens?.[project.projectId] : void 0;
23489
23584
  if (guestToken) return deployVersion(root, { ...ctx, config: { ...ctx.config, token: guestToken } }, opts, true);
23585
+ if (project?.projectId) throw new CliError(messages(root)("guestAccountSite"), 1, "auth");
23490
23586
  const interactive = Boolean(process.stdin.isTTY && process.stdout.isTTY);
23491
23587
  if (!opts.guest && !(interactive && await askGuest(root))) {
23492
23588
  throw new CliError(messages(root)("deployNotLoggedIn"), 1, "auth");
@@ -23498,7 +23594,7 @@ async function deployCommand(root, ctx, opts) {
23498
23594
  try {
23499
23595
  return await deployVersion(root, ctx, opts, false);
23500
23596
  } catch (e2) {
23501
- if (!claim2 || !(e2 instanceof CliError) || e2.category !== "forbidden") throw e2;
23597
+ if (!claim2 || !(e2 instanceof CliError) || e2.category !== "forbidden" && e2.category !== "not_found") throw e2;
23502
23598
  const t = messages(root);
23503
23599
  const url2 = ctx.config.guestClaims?.[claim2];
23504
23600
  throw new CliError(url2 ? t("guestClaimFirst", { url: url2 }) : t("guestClaimFirstNoLink"), 1, "guest_unclaimed");
@@ -23619,7 +23715,7 @@ async function claimCommand(root, ctx, opts) {
23619
23715
 
23620
23716
  // src/commands/samples.ts
23621
23717
  import { createInterface as createInterface3 } from "readline/promises";
23622
- import { existsSync as existsSync5, mkdirSync as mkdirSync3, readdirSync as readdirSync3, writeFileSync as writeFileSync5 } from "fs";
23718
+ import { existsSync as existsSync6, mkdirSync as mkdirSync3, readdirSync as readdirSync3, writeFileSync as writeFileSync5 } from "fs";
23623
23719
  import { dirname, join as join6, relative, resolve, sep } from "path";
23624
23720
  import { gunzipSync } from "zlib";
23625
23721
  var SAMPLES = [
@@ -23641,7 +23737,7 @@ function listSamples(apiUrl) {
23641
23737
  out(`See them live: ${apiUrl}/samples`);
23642
23738
  }
23643
23739
  function filesIn(dir) {
23644
- return existsSync5(dir) ? readdirSync3(dir).filter((f) => !IGNORED.has(f)) : [];
23740
+ return existsSync6(dir) ? readdirSync3(dir).filter((f) => !IGNORED.has(f)) : [];
23645
23741
  }
23646
23742
  function freeFolder(cwd, key) {
23647
23743
  for (let n2 = 1; ; n2++) {
@@ -23724,7 +23820,7 @@ async function samplesCommand(cwd, ctx, key, opts = {}) {
23724
23820
  }
23725
23821
  const written = await downloadSample(ctx, sample.key, root);
23726
23822
  const base = resolve(root);
23727
- const hasPackage = existsSync5(join6(base, "package.json"));
23823
+ const hasPackage = existsSync6(join6(base, "package.json"));
23728
23824
  out(base === resolve(cwd) ? `Downloaded the ${sample.key} sample: ${written} files.` : `Downloaded the ${sample.key} sample into the new folder ${relative(cwd, base)}: ${written} files. Work in that folder from now on.`);
23729
23825
  out("");
23730
23826
  const t = messages(base);
@@ -23796,18 +23892,23 @@ async function versionsCommand(root, ctx) {
23796
23892
  const sorted = [...versions].sort((a, b) => b.number - a.number);
23797
23893
  out("Versions");
23798
23894
  for (const v of sorted) {
23895
+ const status = v.status === "replaced" ? stateName("replaced", root) : v.status;
23799
23896
  const cols = [
23800
23897
  `v${v.number}`.padEnd(6),
23801
23898
  (v.isLive ? "live" : "").padEnd(6),
23802
- v.status.padEnd(10),
23899
+ status.padEnd(10),
23803
23900
  shortDate(v.createdAt).padEnd(18),
23804
23901
  formatBytes(v.sizeBytes).padEnd(10),
23805
23902
  v.message
23806
23903
  ];
23807
23904
  out(` ${cols.join("")}`);
23808
23905
  if (v.error) out(` ${v.error}`);
23906
+ if (v.migrationStatus === "waiting") out(` ${messages(root)("sqlWaitingLine", { url: versionsPage(ctx, projectId) })}`);
23809
23907
  }
23810
23908
  }
23909
+ function versionsPage(ctx, projectId) {
23910
+ return `${ctx.apiUrl}/project/${encodeURIComponent(projectId)}/versions`;
23911
+ }
23811
23912
  async function changeLiveCommand(root, ctx, version2, kind) {
23812
23913
  requireToken(ctx);
23813
23914
  const projectId = linkedProjectId(root);
@@ -23835,6 +23936,10 @@ async function statusCommand(root, ctx) {
23835
23936
  [t("queue"), s.queue.running ? t("buildingNow") : s.queue.position > 0 ? queueText(s.queue.position, root) : t("idle")]
23836
23937
  ])
23837
23938
  );
23939
+ if (s.latest?.migrationStatus === "waiting") {
23940
+ out();
23941
+ out(t("sqlWaitingLine", { url: versionsPage(ctx, projectId) }));
23942
+ }
23838
23943
  if (s.latest?.status !== "failed") return;
23839
23944
  out();
23840
23945
  out(t("buildFailed", { n: s.latest.number }));
@@ -23850,7 +23955,7 @@ async function statusCommand(root, ctx) {
23850
23955
 
23851
23956
  // src/commands/start.ts
23852
23957
  import { spawnSync as spawnSync2 } from "child_process";
23853
- import { existsSync as existsSync6, writeFileSync as writeFileSync6 } from "fs";
23958
+ import { existsSync as existsSync7, writeFileSync as writeFileSync6 } from "fs";
23854
23959
  import { join as join7, relative as relative2, resolve as resolve2 } from "path";
23855
23960
  var REASONS = { start_not_found: "startNotFound", start_expired: "startExpired", start_used_up: "startUsedUp" };
23856
23961
  var reason = (code) => messages(process.cwd())(REASONS[code] ?? "startNotFound");
@@ -23889,7 +23994,7 @@ async function startCommand(cwd, ctx, id, opts = {}) {
23889
23994
  if (sample && !sameSite) {
23890
23995
  const written = await downloadSample(ctx, sample.key, root);
23891
23996
  done.push(t("startDownloaded", { key: sample.key, n: written }));
23892
- if (existsSync6(join7(root, "package.json"))) {
23997
+ if (existsSync7(join7(root, "package.json"))) {
23893
23998
  if (opts.install !== false && install(root)) done.push(t("startInstalled"));
23894
23999
  else steps.unshift({ cmd: "npm install", say: "Installs the sample's packages." });
23895
24000
  }
@@ -23960,6 +24065,25 @@ async function addonsCommand(root, ctx) {
23960
24065
  }
23961
24066
  out(`Next: ${r.next}. Ask the site owner if they want to start it now, then run: npx deployai@latest addon next`);
23962
24067
  }
24068
+ async function addonBuyCommand(root, ctx, key) {
24069
+ requireToken(ctx);
24070
+ const projectId = linkedProjectId2(root);
24071
+ const name = key.trim().toLowerCase();
24072
+ const r = await apiRequest(ctx, `/api/cli/addons?projectId=${encodeURIComponent(projectId)}`, AddonsResponse);
24073
+ if (!r.open) {
24074
+ out("The DeployAI marketplace is not open yet. Add-ons open soon.");
24075
+ return;
24076
+ }
24077
+ const a = r.addons.find((x) => x.key === name);
24078
+ if (!a) throw new CliError(`No add-on named ${name}. See the list: npx deployai@latest addons`, 1, "unknown");
24079
+ if (a.owned) {
24080
+ out(`This site already has ${a.name}. To run it: npx deployai@latest addon run ${a.key}`);
24081
+ return;
24082
+ }
24083
+ if (!a.buyUrl) throw new CliError(`${a.name} is not for sale right now.`, 1, "not_for_sale");
24084
+ out(`Send this link to the site owner. They pay once for this site, then run: npx deployai@latest addon run ${a.key}`);
24085
+ out(a.buyUrl);
24086
+ }
23963
24087
  function printAddon(a) {
23964
24088
  out(`DeployAI add-on ${a.position} of ${a.total}: ${a.name} (${a.key}, run ${a.runId}).`);
23965
24089
  out("Follow these instructions step by step, with the site owner:");
@@ -24094,7 +24218,7 @@ function buildProgram() {
24094
24218
  program.command("start").description("start from the line copied on the DeployAI site: prepares this folder with every choice made there, then prints the steps").argument("<id>", "the code from the copied line").option("--here", "put a new site in this folder even when it has files").option("--no-install", "do not run npm install for the sample").action((id, opts, cmd) => startCommand(root(cmd), ctx(cmd), id, opts));
24095
24219
  program.command("samples").description("list the official sample sites, or download one (into this folder when empty, else a new one)").argument("[name]", "landing, business, form, crud, shop or admin").argument("[folder]", "put the sample in this folder (default: this one when empty, else asks or makes a new one)").option("--force", "write into this folder even when it has files").action((name, folder, opts, cmd) => samplesCommand(root(cmd), ctx(cmd), name, { ...opts, folder }));
24096
24220
  program.command("login").description("log in through the browser").action((_opts, cmd) => loginCommand(ctx(cmd), { root: root(cmd) }));
24097
- program.command("logout").description("remove the login token from this computer").action(() => logoutCommand());
24221
+ program.command("logout").description("cancel the login token and remove it from this computer").action((_opts, cmd) => logoutCommand(ctx(cmd)));
24098
24222
  program.command("whoami").description("show the logged in user").action((_opts, cmd) => whoamiCommand(ctx(cmd)));
24099
24223
  program.command("telemetry").description("show what is sent, or turn telemetry on or off").argument("[action]", "status, on or off", "status").action((action) => telemetryCommand(action));
24100
24224
  program.command("link").description("link this folder to a DeployAI project").argument("[project]", "project slug or id").option("--create <name>", "create a new project and link it").action(
@@ -24111,6 +24235,7 @@ function buildProgram() {
24111
24235
  const addon = program.command("addon").description("run the add-ons of this site, one at a time");
24112
24236
  addon.command("next").description("print the next add-on for your agent").action((_opts, cmd) => addonNextCommand(root(cmd), ctx(cmd)));
24113
24237
  addon.command("run").description("print one add-on by name").argument("<name>", "the add-on name, as npx deployai@latest addons lists it").action((name, _opts, cmd) => addonRunCommand(root(cmd), ctx(cmd), name));
24238
+ addon.command("buy").description("print the link where the site owner buys an add-on").argument("<name>", "the add-on name, as npx deployai@latest addons lists it").action((name, _opts, cmd) => addonBuyCommand(root(cmd), ctx(cmd), name));
24114
24239
  addon.command("done").description("save the report of a finished add-on and print the next one").argument("<runId>", "the run id printed at the top of the add-on").option("--status <status>", "done, failed or skipped", "done").option("--report <file>", "the JSON report, for example .deployai/addon-report.json").action((runId, opts, cmd) => addonDoneCommand(root(cmd), ctx(cmd), runId, opts));
24115
24240
  program.command("speed").description("Google PageSpeed test of a page: lab results, real visitor data, biggest opportunities").argument("<url>", "the page address").option("--desktop", "test a desktop load instead of a phone").action((url2, opts, cmd) => speedCommand(ctx(cmd), url2, opts));
24116
24241
  const env = program.command("env").description("the site's environment variables: secrets by default, never shown again");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "deployai",
3
- "version": "0.1.0-beta.2",
3
+ "version": "0.1.0-beta.3",
4
4
  "description": "Check a site built with an AI editor, see what has to change, and publish it to DeployAI. Hebrew first.",
5
5
  "keywords": ["deployai", "deploy", "hosting", "cli", "ai", "claude-code", "cursor", "copilot", "codex", "antigravity", "static-site", "astro", "vite", "hebrew", "israel"],
6
6
  "homepage": "https://deployai.co.il/developers",