@amirhosseinnateghi/vibed-cli 0.1.0 → 0.1.2

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 (2) hide show
  1. package/dist/index.js +268 -140
  2. package/package.json +5 -2
package/dist/index.js CHANGED
@@ -6018,141 +6018,6 @@ import { dirname, extname, isAbsolute, join as join2, resolve } from "node:path"
6018
6018
 
6019
6019
  // ../sandbox/src/index.ts
6020
6020
  var import_node_html_parser = __toESM(require_dist(), 1);
6021
- var FORBIDDEN_API_PATTERNS = [
6022
- { re: /getUserMedia/, name: "getUserMedia" },
6023
- { re: /navigator\s*\.\s*geolocation/, name: "geolocation" },
6024
- { re: /new\s+Notification|Notification\s*\.\s*requestPermission/, name: "Notification" },
6025
- { re: /serviceWorker/, name: "serviceWorker" },
6026
- { re: /openDatabase/, name: "openDatabase" },
6027
- { re: /PaymentRequest|navigator\s*\.\s*payments/, name: "PaymentRequest" },
6028
- { re: /navigator\s*\.\s*credentials|PublicKeyCredential|WebAuthn/, name: "WebAuthn" },
6029
- { re: /requestPointerLock/, name: "pointerLock" }
6030
- // allowed via gesture only; flag
6031
- ];
6032
- var TOP_NAV_PATTERNS = [
6033
- /window\s*\.\s*top/,
6034
- /window\s*\.\s*parent\s*\.\s*location/,
6035
- /\btop\s*\.\s*location/,
6036
- /\bparent\s*\.\s*location/
6037
- ];
6038
- var BRAND_NAMES = [
6039
- "google",
6040
- "gmail",
6041
- "apple",
6042
- "icloud",
6043
- "microsoft",
6044
- "outlook",
6045
- "paypal",
6046
- "facebook",
6047
- "instagram",
6048
- "telegram",
6049
- "whatsapp",
6050
- "metamask",
6051
- "binance",
6052
- "coinbase"
6053
- ];
6054
- function extractExternalHosts(html) {
6055
- const scrubbed = html.replace(/\bxmlns(:[a-z0-9-]+)?\s*=\s*("[^"]*"|'[^']*')/gi, "").replace(/<!DOCTYPE[^>]*>/gi, "");
6056
- const hosts = /* @__PURE__ */ new Set();
6057
- const re = /https?:\/\/([a-z0-9.-]+)/gi;
6058
- let m;
6059
- while (m = re.exec(scrubbed)) {
6060
- const host = m[1]?.toLowerCase();
6061
- if (host) hosts.add(host);
6062
- }
6063
- return [...hosts];
6064
- }
6065
- function looksLikeHtml(root) {
6066
- const hasEl = root.querySelector("*") != null;
6067
- return hasEl;
6068
- }
6069
- function validate(html, opts) {
6070
- const errors = [];
6071
- const warnings = [];
6072
- const size = Buffer.byteLength(html, "utf8");
6073
- if (size > opts.maxBytes) {
6074
- errors.push({
6075
- code: "SIZE_EXCEEDED",
6076
- message: `Artifact is ${size} bytes; limit is ${opts.maxBytes}.`
6077
- });
6078
- }
6079
- const root = (0, import_node_html_parser.parse)(html, {
6080
- lowerCaseTagName: true,
6081
- comment: true,
6082
- voidTag: { closingSlash: true }
6083
- });
6084
- if (!looksLikeHtml(root)) {
6085
- errors.push({ code: "NOT_HTML", message: "Content is not parseable as HTML." });
6086
- return { ok: false, errors, warnings };
6087
- }
6088
- if (root.querySelector("iframe, frame, object, embed")) {
6089
- errors.push({
6090
- code: "FRAME_TAG",
6091
- message: "Frames are not allowed (iframe/frame/object/embed)."
6092
- });
6093
- }
6094
- if (root.querySelector("base")) {
6095
- errors.push({ code: "BASE_TAG", message: "<base> tags are not allowed." });
6096
- }
6097
- for (const meta of root.querySelectorAll("meta")) {
6098
- if ((meta.getAttribute("http-equiv") || "").toLowerCase() === "refresh") {
6099
- errors.push({ code: "META_REFRESH", message: "meta-refresh redirects are not allowed." });
6100
- break;
6101
- }
6102
- }
6103
- for (const input of root.querySelectorAll("input")) {
6104
- if ((input.getAttribute("type") || "").toLowerCase() === "password") {
6105
- errors.push({
6106
- code: "PASSWORD_INPUT",
6107
- message: 'Password inputs are not allowed (<input type="password">).'
6108
- });
6109
- break;
6110
- }
6111
- }
6112
- for (const re of TOP_NAV_PATTERNS) {
6113
- if (re.test(html)) {
6114
- errors.push({
6115
- code: "TOP_NAVIGATION",
6116
- message: "Top-level navigation attempts are not allowed.",
6117
- detail: re.source
6118
- });
6119
- break;
6120
- }
6121
- }
6122
- for (const { re, name } of FORBIDDEN_API_PATTERNS) {
6123
- if (re.test(html)) {
6124
- errors.push({
6125
- code: "FORBIDDEN_API",
6126
- message: `Forbidden API used: ${name}.`,
6127
- detail: name
6128
- });
6129
- }
6130
- }
6131
- const hosts = extractExternalHosts(html);
6132
- const offending = hosts.filter(
6133
- (h) => !opts.allowedOrigins.some((allowed) => h === allowed || h.endsWith("." + allowed))
6134
- );
6135
- for (const host of offending) {
6136
- errors.push({
6137
- code: "EXTERNAL_URL",
6138
- message: `External URL not in allowlist: ${host}`,
6139
- detail: host
6140
- });
6141
- }
6142
- const lower = html.toLowerCase();
6143
- const hasCredField = root.querySelector('input[type="email"], input[type="text"], input[type="tel"]') != null && /(password|passcode|otp|verify|sign\s*in|log\s*in|account)/i.test(html);
6144
- if (hasCredField) {
6145
- const brand = BRAND_NAMES.find((b) => lower.includes(b));
6146
- if (brand) {
6147
- warnings.push({
6148
- code: "BRAND_CREDENTIAL",
6149
- message: `Possible phishing: brand "${brand}" near a credential field.`,
6150
- detail: brand
6151
- });
6152
- }
6153
- }
6154
- return { ok: errors.length === 0, errors, warnings };
6155
- }
6156
6021
 
6157
6022
  // ../shared/src/constants.ts
6158
6023
  var CATEGORIES = [
@@ -6181,6 +6046,17 @@ var config = {
6181
6046
  MODERATION_AUTOPUBLISH: boolEnv("MODERATION_AUTOPUBLISH", true),
6182
6047
  BUILDER_MODE: strEnv("BUILDER_MODE", "mock"),
6183
6048
  // 'mock' | 'live'
6049
+ // Builder LLM — non-secret routing only (this object ships to the client).
6050
+ // The platform API key is read server-side from process.env.BUILDER_API_KEY.
6051
+ // Which platform provider serves the builder: 'gapgpt' | 'openrouter'.
6052
+ BUILDER_PROVIDER: strEnv("BUILDER_PROVIDER", "gapgpt"),
6053
+ BUILDER_BASE_URL: strEnv("BUILDER_BASE_URL", "https://api.gapgpt.app/v1"),
6054
+ BUILDER_MODEL: strEnv("BUILDER_MODEL", "claude-sonnet-4-20250514"),
6055
+ // Light model that auto-tags the experience category. Falls back to a keyword
6056
+ // heuristic if the call fails.
6057
+ BUILDER_CLASSIFIER_MODEL: strEnv("BUILDER_CLASSIFIER_MODEL", "gemini-2.5-flash"),
6058
+ BUILDER_MAX_TOKENS: intEnv("BUILDER_MAX_TOKENS", 8192),
6059
+ BUILDER_MAX_MESSAGES: intEnv("BUILDER_MAX_MESSAGES", 30),
6184
6060
  DEFAULT_LOCALE: strEnv("DEFAULT_LOCALE", "en")
6185
6061
  };
6186
6062
  function strEnv(key, fallback) {
@@ -6203,6 +6079,37 @@ function boolEnv(key, fallback) {
6203
6079
  return v === "true" || v === "1";
6204
6080
  }
6205
6081
 
6082
+ // ../shared/src/policy.ts
6083
+ function boolEnv2(key, fallback) {
6084
+ const v = typeof process !== "undefined" ? process.env[key] : void 0;
6085
+ if (v == null || v === "") return fallback;
6086
+ return v === "true" || v === "1";
6087
+ }
6088
+ var POST_RULES = [
6089
+ { id: "size_limit", code: "SIZE_EXCEEDED", title: "Size limit", default: true, env: "RULE_SIZE_LIMIT" },
6090
+ { id: "frames", code: "FRAME_TAG", title: "No frames", default: true, env: "RULE_FRAMES" },
6091
+ { id: "base_tag", code: "BASE_TAG", title: "No <base> tag", default: true, env: "RULE_BASE_TAG" },
6092
+ { id: "meta_refresh", code: "META_REFRESH", title: "No meta-refresh", default: true, env: "RULE_META_REFRESH" },
6093
+ { id: "password_input", code: "PASSWORD_INPUT", title: "No password inputs", default: true, env: "RULE_PASSWORD_INPUT" },
6094
+ { id: "top_navigation", code: "TOP_NAVIGATION", title: "No top-navigation", default: true, env: "RULE_TOP_NAVIGATION" },
6095
+ { id: "forbidden_apis", code: "FORBIDDEN_API", title: "No forbidden APIs (payments, background, capture\u2026)", default: true, env: "RULE_FORBIDDEN_APIS" },
6096
+ { id: "external_urls", code: "EXTERNAL_URL", title: "No off-allowlist external URLs", default: true, env: "RULE_EXTERNAL_URLS" },
6097
+ { id: "external_scripts", code: "EXTERNAL_SCRIPT", title: "No creator-selected JS packages (external <script>)", default: false, env: "RULE_EXTERNAL_SCRIPTS" },
6098
+ { id: "external_links", code: "EXTERNAL_LINK", title: "No direct external links (<a> off-site)", default: false, env: "RULE_EXTERNAL_LINKS" },
6099
+ // Enforced by injecting a viewport clip at sanitize (not a reject) — a static
6100
+ // scan can't tell whether a post actually scrolls, so we make it can't.
6101
+ { id: "no_vertical_scroll", code: "SANITIZE_CLIP", title: "No vertical page scroll (fit the viewport)", default: true, env: "RULE_NO_VERTICAL_SCROLL" }
6102
+ ];
6103
+ function resolvePolicy() {
6104
+ const rules = {};
6105
+ for (const r of POST_RULES) rules[r.id] = boolEnv2(r.env, r.default);
6106
+ return { enabled: boolEnv2("POST_RESTRICTIONS_ENABLED", true), rules };
6107
+ }
6108
+ var postPolicy = resolvePolicy();
6109
+ function ruleActive(id, policy = postPolicy) {
6110
+ return policy.enabled && policy.rules[id] === true;
6111
+ }
6112
+
6206
6113
  // ../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/external.js
6207
6114
  var external_exports = {};
6208
6115
  __export(external_exports, {
@@ -10284,8 +10191,19 @@ var createBuildSchema = external_exports.object({
10284
10191
  mode: external_exports.enum(["ai", "remix"]),
10285
10192
  remixParentId: external_exports.string().uuid().optional()
10286
10193
  });
10194
+ var builderAttachmentSchema = external_exports.object({
10195
+ kind: external_exports.enum(["image", "file"]),
10196
+ name: external_exports.string().max(200),
10197
+ dataUrl: external_exports.string().max(8e6).optional(),
10198
+ // ~6MB image as base64 data URL
10199
+ text: external_exports.string().max(5e4).optional()
10200
+ });
10287
10201
  var builderMessageSchema = external_exports.object({
10288
- content: external_exports.string().min(1).max(4e3)
10202
+ content: external_exports.string().max(4e3).default(""),
10203
+ attachments: external_exports.array(builderAttachmentSchema).max(6).optional(),
10204
+ model: external_exports.string().max(100).optional()
10205
+ }).refine((v) => v.content.trim().length > 0 || (v.attachments?.length ?? 0) > 0, {
10206
+ message: "Send a message or an attachment."
10289
10207
  });
10290
10208
  var importSchema = external_exports.object({
10291
10209
  html: external_exports.string().min(1).optional(),
@@ -10296,6 +10214,7 @@ var importSchema = external_exports.object({
10296
10214
  var saveSchema = external_exports.object({
10297
10215
  collectionId: external_exports.string().uuid().optional()
10298
10216
  });
10217
+ var AI_PROVIDERS = ["gapgpt", "moonshot", "openai", "openrouter"];
10299
10218
  var collectionSchema = external_exports.object({
10300
10219
  name: external_exports.string().min(1).max(60)
10301
10220
  });
@@ -10313,8 +10232,8 @@ var patchMeSchema = external_exports.object({
10313
10232
  // once
10314
10233
  });
10315
10234
  var aiConnectionSchema = external_exports.object({
10316
- provider: external_exports.enum(["openai", "anthropic"]),
10317
- apiKey: external_exports.string().min(8)
10235
+ provider: external_exports.enum(AI_PROVIDERS),
10236
+ apiKey: external_exports.string().trim().min(12).max(300)
10318
10237
  });
10319
10238
  var feedQuerySchema = external_exports.object({
10320
10239
  tab: external_exports.enum(["explore", "following"]).default("explore"),
@@ -10330,6 +10249,24 @@ var searchQuerySchema = external_exports.object({
10330
10249
  limit: external_exports.coerce.number().int().min(1).max(30).default(15)
10331
10250
  });
10332
10251
 
10252
+ // ../shared/src/models.ts
10253
+ var BUILDER_MODELS = [
10254
+ // ── Free (selectable now) — good quality, reasonable price ─────────
10255
+ { id: "claude-sonnet-4-20250514", label: "Claude Sonnet 4", tier: "free", note: "Best for polished HTML", vision: true },
10256
+ { id: "gemini-2.5-flash", label: "Gemini 2.5 Flash", tier: "free", note: "Fast \xB7 great UI \xB7 cheap", vision: true },
10257
+ { id: "gpt-4o-mini", label: "GPT-4o mini", tier: "free", note: "Cheap & reliable", vision: true },
10258
+ { id: "gpt-5-mini", label: "GPT-5 mini", tier: "free", note: "Newer, capable", vision: true },
10259
+ { id: "gpt-5.1-codex", label: "GPT-5.1 Codex", tier: "free", note: "Code specialist" },
10260
+ { id: "deepseek-v4-pro", label: "DeepSeek V4 Pro", tier: "free", note: "Strong coder \xB7 great value" },
10261
+ { id: "deepseek-v4-flash", label: "DeepSeek V4 Flash", tier: "free", note: "Fast \xB7 very cheap" },
10262
+ { id: "gapgpt-qwen-3.6", label: "Qwen 3.6", tier: "free", note: "Good value" },
10263
+ // ── Subscription (locked until billing is enabled) — premium ──────
10264
+ { id: "claude-opus-4-8", label: "Claude Opus 4.8", tier: "subscription", note: "Top quality", vision: true },
10265
+ { id: "gpt-5.2-pro", label: "GPT-5.2 Pro", tier: "subscription", note: "Premium" },
10266
+ { id: "gemini-3-pro-preview", label: "Gemini 3 Pro", tier: "subscription", note: "Premium", vision: true }
10267
+ ];
10268
+ var FREE_IDS = new Set(BUILDER_MODELS.filter((m) => m.tier === "free").map((m) => m.id));
10269
+
10333
10270
  // ../shared/src/tokens.ts
10334
10271
  var colors = {
10335
10272
  brand: "#6C4CF1",
@@ -10361,6 +10298,175 @@ var toggle = {
10361
10298
  knobTo: "22px"
10362
10299
  };
10363
10300
 
10301
+ // ../sandbox/src/index.ts
10302
+ var FORBIDDEN_API_PATTERNS = [
10303
+ { re: /getUserMedia/, name: "getUserMedia" },
10304
+ { re: /navigator\s*\.\s*geolocation/, name: "geolocation" },
10305
+ { re: /new\s+Notification|Notification\s*\.\s*requestPermission/, name: "Notification" },
10306
+ { re: /serviceWorker/, name: "serviceWorker" },
10307
+ { re: /openDatabase/, name: "openDatabase" },
10308
+ { re: /PaymentRequest|navigator\s*\.\s*payments/, name: "PaymentRequest" },
10309
+ { re: /navigator\s*\.\s*credentials|PublicKeyCredential|WebAuthn/, name: "WebAuthn" },
10310
+ { re: /requestPointerLock/, name: "pointerLock" }
10311
+ // allowed via gesture only; flag
10312
+ ];
10313
+ var TOP_NAV_PATTERNS = [
10314
+ /window\s*\.\s*top/,
10315
+ /window\s*\.\s*parent\s*\.\s*location/,
10316
+ /\btop\s*\.\s*location/,
10317
+ /\bparent\s*\.\s*location/
10318
+ ];
10319
+ var BRAND_NAMES = [
10320
+ "google",
10321
+ "gmail",
10322
+ "apple",
10323
+ "icloud",
10324
+ "microsoft",
10325
+ "outlook",
10326
+ "paypal",
10327
+ "facebook",
10328
+ "instagram",
10329
+ "telegram",
10330
+ "whatsapp",
10331
+ "metamask",
10332
+ "binance",
10333
+ "coinbase"
10334
+ ];
10335
+ function extractExternalHosts(html) {
10336
+ const scrubbed = html.replace(/\bxmlns(:[a-z0-9-]+)?\s*=\s*("[^"]*"|'[^']*')/gi, "").replace(/<!DOCTYPE[^>]*>/gi, "");
10337
+ const hosts = /* @__PURE__ */ new Set();
10338
+ const re = /https?:\/\/([a-z0-9.-]+)/gi;
10339
+ let m;
10340
+ while (m = re.exec(scrubbed)) {
10341
+ const host = m[1]?.toLowerCase();
10342
+ if (host) hosts.add(host);
10343
+ }
10344
+ return [...hosts];
10345
+ }
10346
+ function looksLikeHtml(root) {
10347
+ const hasEl = root.querySelector("*") != null;
10348
+ return hasEl;
10349
+ }
10350
+ function validate(html, opts) {
10351
+ const errors = [];
10352
+ const warnings = [];
10353
+ const size = Buffer.byteLength(html, "utf8");
10354
+ const policy = opts.policy ?? postPolicy;
10355
+ const on = (id) => ruleActive(id, policy);
10356
+ if (on("size_limit") && size > opts.maxBytes) {
10357
+ errors.push({
10358
+ code: "SIZE_EXCEEDED",
10359
+ message: `Artifact is ${size} bytes; limit is ${opts.maxBytes}.`
10360
+ });
10361
+ }
10362
+ const root = (0, import_node_html_parser.parse)(html, {
10363
+ lowerCaseTagName: true,
10364
+ comment: true,
10365
+ voidTag: { closingSlash: true }
10366
+ });
10367
+ if (!looksLikeHtml(root)) {
10368
+ errors.push({ code: "NOT_HTML", message: "Content is not parseable as HTML." });
10369
+ return { ok: false, errors, warnings };
10370
+ }
10371
+ if (on("frames") && root.querySelector("iframe, frame, object, embed")) {
10372
+ errors.push({
10373
+ code: "FRAME_TAG",
10374
+ message: "Frames are not allowed (iframe/frame/object/embed)."
10375
+ });
10376
+ }
10377
+ if (on("base_tag") && root.querySelector("base")) {
10378
+ errors.push({ code: "BASE_TAG", message: "<base> tags are not allowed." });
10379
+ }
10380
+ if (on("meta_refresh")) {
10381
+ for (const meta of root.querySelectorAll("meta")) {
10382
+ if ((meta.getAttribute("http-equiv") || "").toLowerCase() === "refresh") {
10383
+ errors.push({ code: "META_REFRESH", message: "meta-refresh redirects are not allowed." });
10384
+ break;
10385
+ }
10386
+ }
10387
+ }
10388
+ if (on("password_input")) {
10389
+ for (const input of root.querySelectorAll("input")) {
10390
+ if ((input.getAttribute("type") || "").toLowerCase() === "password") {
10391
+ errors.push({
10392
+ code: "PASSWORD_INPUT",
10393
+ message: 'Password inputs are not allowed (<input type="password">).'
10394
+ });
10395
+ break;
10396
+ }
10397
+ }
10398
+ }
10399
+ if (on("top_navigation")) {
10400
+ for (const re of TOP_NAV_PATTERNS) {
10401
+ if (re.test(html)) {
10402
+ errors.push({
10403
+ code: "TOP_NAVIGATION",
10404
+ message: "Top-level navigation attempts are not allowed.",
10405
+ detail: re.source
10406
+ });
10407
+ break;
10408
+ }
10409
+ }
10410
+ }
10411
+ if (on("forbidden_apis")) {
10412
+ for (const { re, name } of FORBIDDEN_API_PATTERNS) {
10413
+ if (re.test(html)) {
10414
+ errors.push({ code: "FORBIDDEN_API", message: `Forbidden API used: ${name}.`, detail: name });
10415
+ }
10416
+ }
10417
+ }
10418
+ if (on("external_urls")) {
10419
+ const hosts = extractExternalHosts(html);
10420
+ const offending = hosts.filter(
10421
+ (h) => !opts.allowedOrigins.some((allowed) => h === allowed || h.endsWith("." + allowed))
10422
+ );
10423
+ for (const host of offending) {
10424
+ errors.push({
10425
+ code: "EXTERNAL_URL",
10426
+ message: `External URL not in allowlist: ${host}`,
10427
+ detail: host
10428
+ });
10429
+ }
10430
+ }
10431
+ if (on("external_scripts")) {
10432
+ for (const s of root.querySelectorAll("script")) {
10433
+ const src = s.getAttribute("src");
10434
+ if (src && /^(https?:)?\/\//i.test(src.trim())) {
10435
+ errors.push({
10436
+ code: "EXTERNAL_SCRIPT",
10437
+ message: `External script not allowed (inline your code): ${src}`,
10438
+ detail: src
10439
+ });
10440
+ }
10441
+ }
10442
+ }
10443
+ if (on("external_links")) {
10444
+ for (const a of root.querySelectorAll("a")) {
10445
+ const href = (a.getAttribute("href") || "").trim();
10446
+ if (/^(https?:)?\/\//i.test(href)) {
10447
+ errors.push({
10448
+ code: "EXTERNAL_LINK",
10449
+ message: `External link not allowed: ${href}`,
10450
+ detail: href
10451
+ });
10452
+ }
10453
+ }
10454
+ }
10455
+ const lower = html.toLowerCase();
10456
+ const hasCredField = root.querySelector('input[type="email"], input[type="text"], input[type="tel"]') != null && /(password|passcode|otp|verify|sign\s*in|log\s*in|account)/i.test(html);
10457
+ if (hasCredField) {
10458
+ const brand = BRAND_NAMES.find((b) => lower.includes(b));
10459
+ if (brand) {
10460
+ warnings.push({
10461
+ code: "BRAND_CREDENTIAL",
10462
+ message: `Possible phishing: brand "${brand}" near a credential field.`,
10463
+ detail: brand
10464
+ });
10465
+ }
10466
+ }
10467
+ return { ok: errors.length === 0, errors, warnings };
10468
+ }
10469
+
10364
10470
  // src/bundle.ts
10365
10471
  var MIME = {
10366
10472
  ".css": "text/css",
@@ -10611,10 +10717,11 @@ async function bundleProject(projectDir, opts = {}) {
10611
10717
  rootDir: dirname(resolved.entry),
10612
10718
  allowedOrigins
10613
10719
  });
10614
- const result = validate(html, { maxBytes, allowedOrigins });
10720
+ const result = validate(html, { maxBytes, allowedOrigins, policy: opts.policy });
10615
10721
  return {
10616
10722
  ok: result.ok,
10617
10723
  html,
10724
+ policy: opts.policy,
10618
10725
  entry: resolved.entry,
10619
10726
  builtWith: resolved.builtWith,
10620
10727
  sizeBytes: Buffer.byteLength(html, "utf8"),
@@ -10634,7 +10741,9 @@ var FIXES = {
10634
10741
  TOP_NAVIGATION: "Remove top-level navigation (window.top / parent.location). The artifact runs sandboxed and can't drive the page it's embedded in.",
10635
10742
  FORBIDDEN_API: "This browser API is blocked in the sandbox (e.g. getUserMedia, geolocation, serviceWorker, Notification, PaymentRequest, WebAuthn). Remove it or gate it behind a no-op fallback.",
10636
10743
  PASSWORD_INPUT: `Password inputs aren't allowed (anti-phishing). Remove <input type="password">.`,
10637
- EXTERNAL_URL: "References a host that isn't on the allowlist \u2014 often a call to your own backend/API or a third-party script. vibed artifacts are a single static file: inline the asset, drop the call, or move it to an allowlisted CDN (fonts.googleapis.com, cdn.jsdelivr.net, unpkg.com, cdnjs.cloudflare.com, fonts.gstatic.com)."
10744
+ EXTERNAL_URL: "References a host that isn't on the allowlist \u2014 often a call to your own backend/API or a third-party script. vibed artifacts are a single static file: inline the asset, drop the call, or move it to an allowlisted CDN (fonts.googleapis.com, cdn.jsdelivr.net, unpkg.com, cdnjs.cloudflare.com, fonts.gstatic.com).",
10745
+ EXTERNAL_SCRIPT: "Loads JavaScript from an external <script src>. Creator-selected packages aren't allowed \u2014 inline the code you need directly into the file.",
10746
+ EXTERNAL_LINK: "Links off the platform (<a href> to another site). Posts can't send viewers elsewhere \u2014 remove the link or make it in-page (e.g. an anchor to a section)."
10638
10747
  };
10639
10748
  var KB = 1024;
10640
10749
  function fmtSize(bytes) {
@@ -10677,6 +10786,12 @@ function formatReport(result) {
10677
10786
  out2.push(`\u2717 Not vibeable yet \u2014 ${result.blockers.length} blocker(s):`);
10678
10787
  out2.push(...summarize(result).lines);
10679
10788
  }
10789
+ if (result.policy && ruleActive("no_vertical_scroll", result.policy)) {
10790
+ out2.push(
10791
+ "",
10792
+ "\u2139 Single screen: the page can't scroll vertically (auto-clipped at publish) \u2014 design it to fit."
10793
+ );
10794
+ }
10680
10795
  if (result.warnings.length) {
10681
10796
  out2.push("", "Flags (published but sent to moderation):");
10682
10797
  for (const w of result.warnings) out2.push(` \u2022 ${w.message}`);
@@ -10736,6 +10851,13 @@ async function apiFetch({ apiBase, token }, method, path, body) {
10736
10851
  }
10737
10852
  return json.data;
10738
10853
  }
10854
+ async function fetchPolicy(apiBase) {
10855
+ try {
10856
+ return await apiFetch({ apiBase }, "GET", "/policy");
10857
+ } catch {
10858
+ return void 0;
10859
+ }
10860
+ }
10739
10861
 
10740
10862
  // src/login.ts
10741
10863
  function openBrowser(url) {
@@ -10889,9 +11011,13 @@ Publish only:
10889
11011
  async function cmdCheck(args) {
10890
11012
  const json = isTrue(args.flags.json);
10891
11013
  const dir2 = resolve2(args._[1] ?? ".");
11014
+ const cfg = await loadConfig({ api: str(args.flags.api) });
11015
+ const policy = isTrue(args.flags["local-policy"]) ? void 0 : await fetchPolicy(cfg.apiBase);
11016
+ if (!json && policy) console.error(`Rules: ${cfg.apiBase}`);
10892
11017
  const result = await bundleProject(dir2, {
10893
11018
  entry: str(args.flags.entry),
10894
11019
  build: !isTrue(args.flags["no-build"]),
11020
+ policy,
10895
11021
  onLog: (m) => !json && console.error(m)
10896
11022
  });
10897
11023
  out(json, formatReport(result), {
@@ -10914,9 +11040,11 @@ async function cmdPublish(args) {
10914
11040
  console.error("Not signed in. Run `vibed login` first.");
10915
11041
  return 1;
10916
11042
  }
11043
+ const policy = await fetchPolicy(cfg.apiBase);
10917
11044
  const result = await bundleProject(dir2, {
10918
11045
  entry: str(args.flags.entry),
10919
11046
  build: !isTrue(args.flags["no-build"]),
11047
+ policy,
10920
11048
  onLog: (m) => !json && console.error(m)
10921
11049
  });
10922
11050
  if (!result.ok || !result.html) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@amirhosseinnateghi/vibed-cli",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "make it vibed — check, bundle, and publish a project to vibed from the terminal",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -11,7 +11,10 @@
11
11
  ".": "./dist/index.js",
12
12
  "./lib": "./src/lib.ts"
13
13
  },
14
- "files": ["dist", "README.md"],
14
+ "files": [
15
+ "dist",
16
+ "README.md"
17
+ ],
15
18
  "publishConfig": {
16
19
  "access": "public"
17
20
  },