@videan/cli 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -120,8 +120,8 @@ var CloudClient = class {
120
120
  const res = await this.request("GET", "/plans");
121
121
  return res.data;
122
122
  }
123
- async request(method, path4, body) {
124
- const url = `${this.baseUrl}${path4}`;
123
+ async request(method, path5, body) {
124
+ const url = `${this.baseUrl}${path5}`;
125
125
  const headers = {
126
126
  "Authorization": `Bearer ${this.apiKey}`,
127
127
  "Content-Type": "application/json"
@@ -290,8 +290,8 @@ var AppsNamespace = class {
290
290
  if (opts?.instance) params.set("instance", opts.instance);
291
291
  if (opts?.nextToken) params.set("next_token", opts.nextToken);
292
292
  const query = params.toString();
293
- const path4 = `/apps/${appId}/logs${query ? `?${query}` : ""}`;
294
- const res = await this.client.request("GET", path4);
293
+ const path5 = `/apps/${appId}/logs${query ? `?${query}` : ""}`;
294
+ const res = await this.client.request("GET", path5);
295
295
  return res.data;
296
296
  }
297
297
  /**
@@ -934,35 +934,100 @@ var logoutCommand = new Command2("logout").description("Forget the saved key, or
934
934
  import { Command as Command3 } from "commander";
935
935
  import inquirer2 from "inquirer";
936
936
  import chalk3 from "chalk";
937
+ import fs2 from "fs-extra";
938
+ import path2 from "node:path";
939
+
940
+ // src/videan-md.ts
937
941
  import fs from "fs-extra";
938
942
  import path from "node:path";
939
- var initCommand = new Command3("init").description("Initialize a new Cloud app in the current directory").action(async () => {
940
- const configPath2 = path.join(process.cwd(), CONFIG_FILE_NAME);
941
- if (await fs.pathExists(configPath2)) {
942
- console.log(chalk3.yellow(`${CONFIG_FILE_NAME} already exists in this directory.`));
943
- const { overwrite } = await inquirer2.prompt([
944
- { type: "confirm", name: "overwrite", message: "Overwrite?", default: false }
945
- ]);
946
- if (!overwrite) return;
943
+ var VIDEAN_MD = `# This project deploys to Videan
944
+
945
+ Deploy: \`videan deploy\` (from this directory). Logs: \`videan logs -f\`.
946
+ Undo a bad deploy: \`videan rollback\`. Full agent guide: https://videan.xyz/llms.txt
947
+
948
+ The contract: serve HTTP on **port 3000**. Runtime (Node/Python/Next.js/Go/
949
+ Dockerfile) is detected from the files \u2014 no config needed. If the app has a
950
+ managed database, read \`DATABASE_URL\` from the environment. Log to stdout.
951
+
952
+ ## Auth \u2014 read this before writing any login code
953
+
954
+ This platform puts a sign-in gate in front of apps unless they are public.
955
+
956
+ - **Gated app (visibility private/org/authenticated \u2014 the default): DO NOT
957
+ build a login page.** Every request arrives with the visitor already
958
+ authenticated: read \`X-User-Id\`, \`X-User-Email\`, \`X-User-Roles\`. If the
959
+ app needs its own user records, find-or-create a local user from
960
+ \`X-User-Email\` and start your session from that \u2014 never render a login
961
+ form when the header is present.
962
+ - **Public app** (\`videan apps set --public\`): the gate is out of the path,
963
+ no headers arrive, bring your own auth if the app needs any. Required for
964
+ anything that must receive webhooks from external services.
965
+
966
+ Secrets: \`videan secrets set KEY=value\` (UPPERCASE keys) \u2014 injected as env
967
+ vars on the next deploy. Never commit credentials to this repo instead.
968
+ `;
969
+ var POINTER = "\n## Videan\n\nThis project deploys to Videan \u2014 see VIDEAN.md for the platform contract (port 3000, auth headers, deploy commands) before writing server or login code.\n";
970
+ async function writeVideanMd(dir) {
971
+ const wrote = [];
972
+ const target = path.join(dir, "VIDEAN.md");
973
+ if (!await fs.pathExists(target)) {
974
+ await fs.writeFile(target, VIDEAN_MD, "utf-8");
975
+ wrote.push("VIDEAN.md");
976
+ }
977
+ for (const name of ["CLAUDE.md", "AGENTS.md"]) {
978
+ const p = path.join(dir, name);
979
+ if (!await fs.pathExists(p)) continue;
980
+ const body = await fs.readFile(p, "utf-8");
981
+ if (body.includes("VIDEAN.md")) continue;
982
+ await fs.appendFile(p, POINTER, "utf-8");
983
+ wrote.push(`${name} (pointer)`);
984
+ }
985
+ return wrote;
986
+ }
987
+
988
+ // src/commands/init.ts
989
+ var initCommand = new Command3("init").description("Initialize a new Cloud app in the current directory").option("--name <name>", "App name (defaults to the directory name)").option("--runtime <runtime>", "One of: " + SUPPORTED_RUNTIMES.join(", ")).option("--entrypoint <file>", "Entry file", "index.ts").option("--yes", "Take the defaults without prompting").action(async (opts) => {
990
+ const configPath2 = path2.join(process.cwd(), CONFIG_FILE_NAME);
991
+ const interactive = process.stdin.isTTY && !opts.yes;
992
+ if (await fs2.pathExists(configPath2)) {
993
+ if (!interactive) {
994
+ console.log(chalk3.yellow(`${CONFIG_FILE_NAME} already exists; leaving it alone.`));
995
+ } else {
996
+ console.log(chalk3.yellow(`${CONFIG_FILE_NAME} already exists in this directory.`));
997
+ const { overwrite } = await inquirer2.prompt([
998
+ { type: "confirm", name: "overwrite", message: "Overwrite?", default: false }
999
+ ]);
1000
+ if (!overwrite) {
1001
+ for (const f of await writeVideanMd(process.cwd())) console.log(chalk3.green(`Wrote ${f}`));
1002
+ return;
1003
+ }
1004
+ }
947
1005
  }
948
- const answers = await inquirer2.prompt([
949
- { type: "input", name: "name", message: "App name:", default: path.basename(process.cwd()) },
950
- {
951
- type: "list",
952
- name: "runtime",
953
- message: "Runtime:",
954
- choices: SUPPORTED_RUNTIMES
955
- },
956
- { type: "input", name: "entrypoint", message: "Entrypoint:", default: "index.ts" }
957
- ]);
958
- const config2 = [
959
- `name: ${answers.name}`,
960
- `runtime: ${answers.runtime}`,
961
- `entrypoint: ${answers.entrypoint}`,
962
- ""
963
- ].join("\n");
964
- await fs.writeFile(configPath2, config2, "utf-8");
965
- console.log(chalk3.green(`Created ${CONFIG_FILE_NAME}`));
1006
+ if (!await fs2.pathExists(configPath2) || interactive) {
1007
+ const answers = interactive ? await inquirer2.prompt([
1008
+ { type: "input", name: "name", message: "App name:", default: opts.name || path2.basename(process.cwd()) },
1009
+ {
1010
+ type: "list",
1011
+ name: "runtime",
1012
+ message: "Runtime:",
1013
+ choices: SUPPORTED_RUNTIMES
1014
+ },
1015
+ { type: "input", name: "entrypoint", message: "Entrypoint:", default: opts.entrypoint }
1016
+ ]) : {
1017
+ name: opts.name || path2.basename(process.cwd()),
1018
+ runtime: opts.runtime || SUPPORTED_RUNTIMES[0],
1019
+ entrypoint: opts.entrypoint
1020
+ };
1021
+ const config2 = [
1022
+ `name: ${answers.name}`,
1023
+ `runtime: ${answers.runtime}`,
1024
+ `entrypoint: ${answers.entrypoint}`,
1025
+ ""
1026
+ ].join("\n");
1027
+ await fs2.writeFile(configPath2, config2, "utf-8");
1028
+ console.log(chalk3.green(`Created ${CONFIG_FILE_NAME}`));
1029
+ }
1030
+ for (const f of await writeVideanMd(process.cwd())) console.log(chalk3.green(`Wrote ${f}`));
966
1031
  });
967
1032
 
968
1033
  // src/commands/deploy.ts
@@ -971,8 +1036,8 @@ import chalk5 from "chalk";
971
1036
  import ora2 from "ora";
972
1037
 
973
1038
  // src/util.ts
974
- import fs2 from "fs-extra";
975
- import path2 from "node:path";
1039
+ import fs3 from "fs-extra";
1040
+ import path3 from "node:path";
976
1041
  import ignore from "ignore";
977
1042
  import chalk4 from "chalk";
978
1043
  var MAX_FILE_BYTES = 10 * 1024 * 1024;
@@ -989,30 +1054,30 @@ async function collectFiles(dir) {
989
1054
  ".DS_Store",
990
1055
  "*.log"
991
1056
  ]);
992
- const gitignorePath = path2.join(dir, ".gitignore");
993
- if (await fs2.pathExists(gitignorePath)) {
994
- const content = await fs2.readFile(gitignorePath, "utf-8");
1057
+ const gitignorePath = path3.join(dir, ".gitignore");
1058
+ if (await fs3.pathExists(gitignorePath)) {
1059
+ const content = await fs3.readFile(gitignorePath, "utf-8");
995
1060
  ig.add(content);
996
1061
  }
997
- const cloudignorePath = path2.join(dir, ".cloudignore");
998
- if (await fs2.pathExists(cloudignorePath)) {
999
- const content = await fs2.readFile(cloudignorePath, "utf-8");
1062
+ const cloudignorePath = path3.join(dir, ".cloudignore");
1063
+ if (await fs3.pathExists(cloudignorePath)) {
1064
+ const content = await fs3.readFile(cloudignorePath, "utf-8");
1000
1065
  ig.add(content);
1001
1066
  }
1002
1067
  const files = {};
1003
1068
  const binaryFiles = {};
1004
1069
  const skipped = [];
1005
1070
  async function walk(currentDir) {
1006
- const entries = await fs2.readdir(currentDir, { withFileTypes: true });
1071
+ const entries = await fs3.readdir(currentDir, { withFileTypes: true });
1007
1072
  for (const entry of entries) {
1008
- const fullPath = path2.join(currentDir, entry.name);
1009
- const relativePath = path2.relative(dir, fullPath);
1073
+ const fullPath = path3.join(currentDir, entry.name);
1074
+ const relativePath = path3.relative(dir, fullPath);
1010
1075
  const ignorePath = entry.isDirectory() ? `${relativePath}/` : relativePath;
1011
1076
  if (ig.ignores(ignorePath)) continue;
1012
1077
  if (entry.isDirectory()) {
1013
1078
  await walk(fullPath);
1014
1079
  } else if (entry.isFile()) {
1015
- const buffer = await fs2.readFile(fullPath);
1080
+ const buffer = await fs3.readFile(fullPath);
1016
1081
  if (buffer.byteLength > MAX_FILE_BYTES) {
1017
1082
  skipped.push({
1018
1083
  path: relativePath,
@@ -1118,6 +1183,16 @@ var deployCommand = new Command4("deploy").description("Deploy the current direc
1118
1183
  }
1119
1184
  if (current.status === "running") {
1120
1185
  spinner.succeed(chalk5.green(`Deployed successfully!`));
1186
+ try {
1187
+ const app = await client4.apps.get(appId);
1188
+ const gated = app.visibility ? app.visibility !== "public" : app.authRequired !== false;
1189
+ console.log(
1190
+ gated ? chalk5.gray(
1191
+ ` Access: ${app.visibility || "org"} \u2014 visitors sign in once at the edge; your app receives X-User-Id / X-User-Email on every request. Don't build a login page.`
1192
+ ) : chalk5.gray(` Access: public \u2014 reachable by anyone; bring your own auth if it needs any.`)
1193
+ );
1194
+ } catch {
1195
+ }
1121
1196
  if (current.url) {
1122
1197
  const url = /^https?:\/\//.test(current.url) ? current.url : `https://${current.url}`;
1123
1198
  console.log(chalk5.cyan(` URL: ${url}`));
@@ -1689,8 +1764,8 @@ orgsCommand.command("invite <email>").description("Invite a member by email").op
1689
1764
  // src/commands/storage.ts
1690
1765
  import { Command as Command13 } from "commander";
1691
1766
  import chalk14 from "chalk";
1692
- import fs3 from "fs-extra";
1693
- import path3 from "node:path";
1767
+ import fs4 from "fs-extra";
1768
+ import path4 from "node:path";
1694
1769
  var storageCommand = new Command13("storage").description("Store files for an app");
1695
1770
  function client() {
1696
1771
  const apiKey = getApiKey();
@@ -1737,12 +1812,12 @@ ${objects.length} object(s), ${human(total)}`));
1737
1812
  });
1738
1813
  storageCommand.command("put <file> [key]").description("Upload a file (key defaults to the file's name)").option("-a, --app <id>", "App ID").option("-t, --type <mime>", "Content type").action(async (file, key, opts) => {
1739
1814
  const appId = requireApp(opts.app);
1740
- if (!await fs3.pathExists(file)) {
1815
+ if (!await fs4.pathExists(file)) {
1741
1816
  console.error(chalk14.red(`No such file: ${file}`));
1742
1817
  process.exit(1);
1743
1818
  }
1744
- const body = await fs3.readFile(file);
1745
- const objectKey = key || path3.basename(file);
1819
+ const body = await fs4.readFile(file);
1820
+ const objectKey = key || path4.basename(file);
1746
1821
  const stored = await client().storage.upload(appId, objectKey, body, {
1747
1822
  contentType: opts.type
1748
1823
  });
@@ -1754,8 +1829,8 @@ storageCommand.command("put <file> [key]").description("Upload a file (key defau
1754
1829
  storageCommand.command("get <key> [file]").description("Download an object (defaults to the key's basename)").option("-a, --app <id>", "App ID").action(async (key, file, opts) => {
1755
1830
  const appId = requireApp(opts.app);
1756
1831
  const bytes = await client().storage.download(appId, key);
1757
- const dest = file || path3.basename(key);
1758
- await fs3.writeFile(dest, bytes);
1832
+ const dest = file || path4.basename(key);
1833
+ await fs4.writeFile(dest, bytes);
1759
1834
  console.log(chalk14.green(`Wrote ${dest}`) + chalk14.dim(` (${human(bytes.length)})`));
1760
1835
  });
1761
1836
  storageCommand.command("rm <key>").description("Delete an object").option("-a, --app <id>", "App ID").action(async (key, opts) => {
@@ -1791,7 +1866,7 @@ storageCommand.command("usage").description("Total objects and bytes stored").op
1791
1866
  // src/commands/email.ts
1792
1867
  import { Command as Command14 } from "commander";
1793
1868
  import chalk15 from "chalk";
1794
- import fs4 from "fs-extra";
1869
+ import fs5 from "fs-extra";
1795
1870
  var emailCommand = new Command14("email").description("Send email from an app");
1796
1871
  function client2() {
1797
1872
  const apiKey = getApiKey();
@@ -1812,7 +1887,7 @@ emailCommand.command("send").description("Send an email").requiredOption("-t, --
1812
1887
  let text = opts.text;
1813
1888
  let html = opts.html;
1814
1889
  if (opts.bodyFile) {
1815
- const contents = await fs4.readFile(opts.bodyFile, "utf-8");
1890
+ const contents = await fs5.readFile(opts.bodyFile, "utf-8");
1816
1891
  if (/\.html?$/i.test(opts.bodyFile)) html = contents;
1817
1892
  else text = contents;
1818
1893
  }
@@ -1857,7 +1932,7 @@ emailCommand.command("quota").description("Show today's sending allowance").opti
1857
1932
  // src/commands/jobs.ts
1858
1933
  import { Command as Command15 } from "commander";
1859
1934
  import chalk16 from "chalk";
1860
- import fs5 from "fs-extra";
1935
+ import fs6 from "fs-extra";
1861
1936
  var jobsCommand = new Command15("jobs").description("Enqueue and inspect background jobs");
1862
1937
  function client3() {
1863
1938
  const apiKey = getApiKey();
@@ -1893,7 +1968,7 @@ function statusLabel(status) {
1893
1968
  jobsCommand.command("enqueue").description("Queue a job for the app to run").option("-a, --app <id>", "App ID").option("-q, --queue <name>", "Queue name (default: default)").option("-p, --path <path>", "Path on the app to POST to (default: /jobs)").option("-d, --data <json>", "JSON payload").option("-f, --file <path>", "Read the JSON payload from a file").option("--delay <seconds>", "Run no earlier than this many seconds from now").option("--attempts <n>", "Attempts before the job is given up on (default: 3)").option("--timeout <seconds>", "How long the handler gets per attempt (default: 60)").option("--key <key>", "Idempotency key -- a repeat returns the same job").option("--json", "Print the raw response").action(async (opts) => {
1894
1969
  const appId = requireApp3(opts.app);
1895
1970
  let payload;
1896
- const raw = opts.file ? await fs5.readFile(opts.file, "utf-8") : opts.data;
1971
+ const raw = opts.file ? await fs6.readFile(opts.file, "utf-8") : opts.data;
1897
1972
  if (raw !== void 0) {
1898
1973
  try {
1899
1974
  payload = JSON.parse(raw);