uniweb 0.12.26 → 0.12.27

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.
@@ -1,230 +1,22 @@
1
1
  /**
2
- * Template Command
2
+ * template — RESERVED (not available on the new backend yet).
3
3
  *
4
- * Publish a site as a cloud template.
4
+ * `uniweb template publish` submitted a site as a cloud template to the legacy
5
+ * registry (the PHP backend + Cloudflare Worker) the CLI no longer talks to. A
6
+ * new-backend equivalent isn't built; when it is, a template is REGISTERED (like
7
+ * a foundation or a schemas package) — the verb will be `register`, not `publish`
8
+ * (`publish` is for SITES only). The command name is kept reserved.
5
9
  *
6
- * Usage:
7
- * uniweb template publish # Reads template name from site.yml `template:` field
8
- * uniweb template publish --name my-tpl # Override template name
9
- * uniweb template publish --title "My Tpl" # Display title
10
- * uniweb template publish --description "A starter template"
11
- * uniweb template publish --registry <url> # Publish to a specific registry URL
10
+ * (Unrelated: scaffolding FROM a template — `uniweb create --template <name>` —
11
+ * is a separate path and is unaffected.)
12
12
  */
13
13
 
14
- import { existsSync } from 'node:fs'
15
- import { readFile, readdir } from 'node:fs/promises'
16
- import { resolve, join, relative } from 'node:path'
17
- import yaml from 'js-yaml'
18
-
19
- import { ensureAuth } from '../utils/auth.js'
20
- import { findWorkspaceRoot, findSites, classifyPackage } from '../utils/workspace.js'
21
- import { isNonInteractive, getCliPrefix } from '../utils/interactive.js'
22
-
23
- const colors = {
24
- reset: '\x1b[0m',
25
- bright: '\x1b[1m',
26
- dim: '\x1b[2m',
27
- cyan: '\x1b[36m',
28
- green: '\x1b[32m',
29
- yellow: '\x1b[33m',
30
- red: '\x1b[31m',
31
- }
32
-
33
- function success(message) {
34
- console.log(`${colors.green}✓${colors.reset} ${message}`)
35
- }
36
-
37
- function error(message) {
38
- console.error(`${colors.red}✗${colors.reset} ${message}`)
39
- }
40
-
41
- function info(message) {
42
- console.log(`${colors.cyan}→${colors.reset} ${message}`)
43
- }
44
-
45
- /**
46
- * Parse a named flag from args.
47
- * @param {string[]} args
48
- * @param {string} flag - e.g. '--name'
49
- * @returns {string|null}
50
- */
51
- function parseFlag(args, flag) {
52
- const idx = args.indexOf(flag)
53
- if (idx === -1 || !args[idx + 1]) return null
54
- return args[idx + 1]
55
- }
56
-
57
- // Build infrastructure files to exclude from templates
58
- const EXCLUDED_FILES = new Set([
59
- 'package.json', 'package-lock.json', 'pnpm-lock.yaml',
60
- 'vite.config.js', 'vite.config.ts',
61
- 'index.html', 'main.js', 'main.ts',
62
- ])
63
- const EXCLUDED_DIRS = new Set([
64
- 'node_modules', 'dist', '.git', '.vite',
65
- ])
66
-
67
- /**
68
- * Resolve the site directory to publish as a template.
69
- *
70
- * Priority:
71
- * 1. In a site directory → use it
72
- * 2. site.yml in cwd (non-package site, e.g. cloud site) → use it
73
- * 3. At workspace root, one site → use it
74
- * 4. At workspace root, multiple → error
75
- * 5. No site → educational error
76
- */
77
- async function resolveSiteDir() {
78
- const cwd = process.cwd()
79
-
80
- // Check if current directory is a site package
81
- const type = await classifyPackage(cwd)
82
- if (type === 'site') return cwd
83
-
84
- // Check for site.yml directly (non-package site, e.g. cloud site)
85
- if (existsSync(join(cwd, 'site.yml'))) return cwd
86
-
87
- // Check workspace
88
- const workspaceRoot = findWorkspaceRoot(cwd)
89
- if (workspaceRoot) {
90
- const sites = await findSites(workspaceRoot)
91
- if (sites.length === 1) return resolve(workspaceRoot, sites[0])
92
- if (sites.length > 1) {
93
- error('Multiple sites found. Run this command from inside the site directory.')
94
- process.exit(1)
95
- }
96
- }
97
-
98
- error('No site found. Run this command from a site directory (must contain site.yml).')
99
- process.exit(1)
100
- }
101
-
102
- /**
103
- * Recursively read all files in a directory, returning { relativePath: base64Content }.
104
- * Skips build infrastructure files and directories.
105
- */
106
- async function readAllFiles(dir, baseDir = dir) {
107
- const files = {}
108
- const entries = await readdir(dir, { withFileTypes: true })
109
-
110
- for (const entry of entries) {
111
- const fullPath = join(dir, entry.name)
112
- if (entry.isDirectory()) {
113
- if (EXCLUDED_DIRS.has(entry.name)) continue
114
- Object.assign(files, await readAllFiles(fullPath, baseDir))
115
- } else if (entry.isFile()) {
116
- if (EXCLUDED_FILES.has(entry.name)) continue
117
- const relPath = relative(baseDir, fullPath)
118
- const content = await readFile(fullPath)
119
- files[relPath] = content.toString('base64')
120
- }
121
- }
122
-
123
- return files
124
- }
125
-
126
- /**
127
- * Main template command dispatcher.
128
- */
129
- export async function template(args = []) {
130
- const subcommand = args[0]
131
-
132
- if (subcommand === 'publish') {
133
- await templatePublish(args.slice(1))
134
- return
135
- }
136
-
137
- const prefix = getCliPrefix()
138
- error(subcommand ? `Unknown subcommand: template ${subcommand}` : 'Missing subcommand')
139
- console.log('')
140
- console.log(`${colors.bright}Usage:${colors.reset}`)
141
- console.log(` ${prefix} template publish Publish a site as a cloud template`)
142
- console.log('')
143
- console.log(`${colors.bright}Options:${colors.reset}`)
144
- console.log(` --name <name> Template registry name (overrides site.yml \`template:\` field)`)
145
- console.log(` --title <title> Display title (overrides site.yml \`name:\` field)`)
146
- console.log(` --description <txt> Description`)
147
- console.log(` --registry <url> Registry URL (default: http://localhost:4001)`)
14
+ export async function template() {
15
+ console.error("\x1b[31m✗\x1b[0m `uniweb template register` isn't available on the new backend yet.")
16
+ console.error(' Submitting a site as a cloud template was retired with the PHP backend.')
17
+ console.error(' A template is REGISTERED, like a foundation — `publish` is for sites only.')
18
+ console.error(' (Scaffolding FROM a template still works: `uniweb create --template <name>`.)')
148
19
  process.exit(1)
149
20
  }
150
21
 
151
- /**
152
- * Publish a site directory as a cloud template.
153
- */
154
- async function templatePublish(args) {
155
- const registryUrl = parseFlag(args, '--registry')
156
- const nameOverride = parseFlag(args, '--name')
157
- const titleOverride = parseFlag(args, '--title')
158
- const descOverride = parseFlag(args, '--description')
159
-
160
- // 1. Resolve site directory
161
- const siteDir = await resolveSiteDir()
162
-
163
- // 2. Read and parse site.yml
164
- const siteYmlPath = join(siteDir, 'site.yml')
165
- if (!existsSync(siteYmlPath)) {
166
- error('No site.yml found in this directory')
167
- process.exit(1)
168
- }
169
-
170
- const siteYmlContent = await readFile(siteYmlPath, 'utf8')
171
- const siteConfig = yaml.load(siteYmlContent) || {}
172
-
173
- // 3. Determine template name: --name flag > site.yml `template:` field > directory name
174
- const templateName = nameOverride || siteConfig.template || siteDir.split('/').pop()
175
-
176
- if (!siteConfig.foundation) {
177
- error('site.yml must declare a foundation')
178
- process.exit(1)
179
- }
180
-
181
- // 4. Collect all content files (skip build infrastructure)
182
- info(`Collecting files from ${colors.dim}${siteDir}${colors.reset}`)
183
- const files = await readAllFiles(siteDir)
184
- const fileCount = Object.keys(files).length
185
-
186
- if (fileCount === 0) {
187
- error('No files found to publish')
188
- process.exit(1)
189
- }
190
-
191
- console.log(` ${colors.dim}${fileCount} files${colors.reset}`)
192
-
193
- // 5. Authenticate
194
- const token = await ensureAuth({ command: 'Publishing template', args })
195
-
196
- // 6. Build payload
197
- const url = registryUrl || process.env.UNIWEB_REGISTRY_URL || 'http://localhost:4001'
198
-
199
- const payload = { name: templateName, files }
200
- if (titleOverride || siteConfig.name) {
201
- payload.title = titleOverride || siteConfig.name
202
- }
203
- if (descOverride) payload.description = descOverride
204
-
205
- // 7. Publish via API
206
- info(`Publishing template ${colors.bright}${templateName}${colors.reset} to ${url}`)
207
-
208
- const headers = { 'Content-Type': 'application/json' }
209
- if (token) headers['Authorization'] = `Bearer ${token}`
210
-
211
- const res = await fetch(`${url}/api/templates`, {
212
- method: 'POST',
213
- headers,
214
- body: JSON.stringify(payload),
215
- })
216
-
217
- const body = await res.json()
218
-
219
- if (!res.ok) {
220
- error(body.error || `Server error (${res.status})`)
221
- process.exit(1)
222
- }
223
-
224
- console.log('')
225
- success(`Published template ${colors.bright}${templateName}${colors.reset}`)
226
- console.log(` ${colors.dim}Foundation: ${body.foundation}${colors.reset}`)
227
- console.log(` ${colors.dim}Files: ${body.filesCount}${colors.reset}`)
228
- }
229
-
230
22
  export default template
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
- "generatedAt": "2026-06-12T00:10:21.149Z",
3
+ "generatedAt": "2026-06-15T03:29:50.221Z",
4
4
  "packages": {
5
5
  "@uniweb/build": {
6
- "version": "0.14.10",
6
+ "version": "0.14.11",
7
7
  "path": "framework/build",
8
8
  "deps": [
9
9
  "@uniweb/content-reader",
@@ -43,7 +43,7 @@
43
43
  "deps": []
44
44
  },
45
45
  "@uniweb/kit": {
46
- "version": "0.9.15",
46
+ "version": "0.9.16",
47
47
  "path": "framework/kit",
48
48
  "deps": [
49
49
  "@uniweb/core",
@@ -69,12 +69,12 @@
69
69
  ]
70
70
  },
71
71
  "@uniweb/scene": {
72
- "version": "0.1.1",
72
+ "version": "0.1.2",
73
73
  "path": "framework/scene",
74
74
  "deps": []
75
75
  },
76
76
  "@uniweb/schemas": {
77
- "version": "0.2.2",
77
+ "version": "0.2.3",
78
78
  "path": "framework/schemas",
79
79
  "deps": []
80
80
  },
@@ -99,7 +99,7 @@
99
99
  "deps": []
100
100
  },
101
101
  "@uniweb/unipress": {
102
- "version": "0.4.14",
102
+ "version": "0.4.15",
103
103
  "path": "framework/unipress",
104
104
  "deps": [
105
105
  "@uniweb/build",
@@ -149,6 +149,17 @@
149
149
  "localization"
150
150
  ]
151
151
  },
152
+ "blog": {
153
+ "name": "Blog",
154
+ "description": "A blog built on the @std/article standard schema. Markdown articles render as a card grid and as full posts via dynamic [slug] routes. The first template to use a shared standard data schema — no custom schema file needed.",
155
+ "tags": [
156
+ "blog",
157
+ "articles",
158
+ "collections",
159
+ "standard-schema",
160
+ "data-schema"
161
+ ]
162
+ },
152
163
  "dynamic": {
153
164
  "name": "Dynamic Data",
154
165
  "description": "Live API data fetching with loading states, transforms, and the portable data pattern",
package/src/index.js CHANGED
@@ -33,7 +33,6 @@ import prompts from 'prompts'
33
33
  // Same pattern as `build` and `docs`.
34
34
  import { i18n } from './commands/i18n.js'
35
35
  import { inspect } from './commands/inspect.js'
36
- import { login } from './commands/login.js'
37
36
  import { invite } from './commands/invite.js'
38
37
  import { handoff } from './commands/handoff.js'
39
38
  import { update } from './commands/update.js'
@@ -134,7 +133,7 @@ function getCliVersion() {
134
133
  // install that's the whole point; delegating to the project-local copy
135
134
  // would align the project to the version it already has, i.e. a no-op.
136
135
  const STANDALONE_COMMANDS = new Set([
137
- 'create', 'clone', '--help', '-h', '--version', '-v', 'login', 'update',
136
+ 'create', 'clone', '--help', '-h', '--version', '-v', 'login', 'logout', 'update',
138
137
  ])
139
138
 
140
139
  /**
@@ -664,11 +663,12 @@ async function main() {
664
663
  return
665
664
  }
666
665
 
667
- // Handle publish command (dynamic import depends on @uniweb/build)
666
+ // Handle publish command CMS-publish a SYNCED site (POST /dev/site/publish).
667
+ // Distinct from `deploy` (file-built host) and `register` (foundation publishing).
668
668
  if (command === 'publish') {
669
669
  const { publish } = await importProjectCommand('./commands/publish.js')
670
- await publish(args.slice(1))
671
- return
670
+ const result = await publish(args.slice(1))
671
+ process.exit(result?.exitCode ?? 0)
672
672
  }
673
673
 
674
674
  // Handle deploy command (dynamic import — depends on @uniweb/build)
@@ -692,18 +692,27 @@ async function main() {
692
692
  return
693
693
  }
694
694
 
695
- // Handle login command. Default targets the NEW backend (username/password);
696
- // `--legacy` runs the old browser/social flow (still used by publish/deploy
697
- // internally via ensureAuth, so it stays reachable).
695
+ // Handle login command the backend (username/password · paste a token ·
696
+ // --token <bearer>). Origin from --backend/--registry > UNIWEB_REGISTER_URL >
697
+ // default, the SAME resolver register/push/pull/deploy use, so a session and
698
+ // the commands that reuse it always target one backend.
698
699
  if (command === 'login') {
699
700
  const loginArgs = args.slice(1)
700
- if (loginArgs.includes('--legacy')) {
701
- await login(loginArgs.filter((a) => a !== '--legacy'))
702
- } else {
703
- const { getRegistryApiBaseUrl } = await import('./utils/config.js')
704
- const { runRegistryLogin } = await import('./utils/registry-auth.js')
705
- await runRegistryLogin({ apiBase: getRegistryApiBaseUrl(), args: loginArgs })
706
- }
701
+ const { resolveBackendOrigin } = await import('./backend/client.js')
702
+ const { readFlagValue } = await import('./utils/args.js')
703
+ const { runRegistryLogin } = await import('./utils/registry-auth.js')
704
+ const originFlag = readFlagValue(loginArgs, '--backend') || readFlagValue(loginArgs, '--registry')
705
+ await runRegistryLogin({ apiBase: resolveBackendOrigin(originFlag), args: loginArgs })
706
+ return
707
+ }
708
+
709
+ // Handle logout command — clear the stored backend session.
710
+ if (command === 'logout') {
711
+ const { clearRegistryAuth, getRegistryAuthPath } = await import('./utils/registry-auth.js')
712
+ const { existsSync } = await import('node:fs')
713
+ const had = existsSync(getRegistryAuthPath())
714
+ await clearRegistryAuth()
715
+ console.log(had ? '\x1b[32m✓\x1b[0m Logged out (cleared the stored session).' : 'Not logged in — nothing to clear.')
707
716
  return
708
717
  }
709
718
 
@@ -714,6 +723,14 @@ async function main() {
714
723
  return
715
724
  }
716
725
 
726
+ // Handle runtime command — `runtime register` uploads a built @uniweb/runtime to
727
+ // the backend's runtime registry (/gateway/runtime/{version}); @std-gated.
728
+ if (command === 'runtime') {
729
+ const { runtime } = await import('./commands/runtime.js')
730
+ const result = await runtime(args.slice(1))
731
+ process.exit(result?.exitCode ?? 0)
732
+ }
733
+
717
734
  // Handle invite command
718
735
  if (command === 'invite') {
719
736
  await invite(args.slice(1))
@@ -1105,24 +1122,20 @@ ${colors.bright}Examples:${colors.reset}
1105
1122
  uniweb deploy --target=preview # Pick named target from deploy.yml
1106
1123
  `,
1107
1124
  publish: `
1108
- ${colors.cyan}${colors.bright}uniweb publish${colors.reset} ${colors.dim}— Publish a foundation to the catalog${colors.reset}
1125
+ ${colors.cyan}${colors.bright}uniweb publish${colors.reset} ${colors.dim}— Publish a synced site (make its backend state live)${colors.reset}
1109
1126
 
1110
1127
  ${colors.bright}Usage:${colors.reset}
1111
- uniweb publish [@org/name] [options]
1128
+ uniweb publish [options]
1112
1129
 
1113
- For site-bound foundations (one foundation, one site), use \`uniweb deploy\`
1114
- insteadit auto-publishes under a site-scoped slot, no naming ceremony.
1130
+ Publishes a site that's synced to the backend (has site.yml::\$uuid from
1131
+ \`uniweb push\`) makes its CURRENT backend state live, including edits made
1132
+ through the app. Run \`uniweb push\` first to include local edits. For a
1133
+ file-only site use \`uniweb deploy\`; to register a FOUNDATION use \`uniweb register\`.
1115
1134
 
1116
1135
  ${colors.bright}Options:${colors.reset}
1117
- --catalog Confirm publish to the public catalog (required in CI)
1118
- --propagate Walk trusting sites' policy waves (default: silent)
1119
- --name <id> Foundation id (overrides package.json::uniweb.id)
1120
- --namespace <ns> Force org-scope namespace (overrides package.json)
1121
- --local Internal: publish to the unicloud mock (see workspace root CLAUDE.md)
1122
- --registry <url> Use a specific registry URL
1123
- --edit-access <p> "open" or "restricted" (default: restricted)
1124
- --dry-run Show what would be published without uploading
1125
- --non-interactive Fail with usage info instead of prompting
1136
+ --backend <url> Backend origin (default: \$UNIWEB_REGISTER_URL or built-in)
1137
+ --token <bearer> Auth bearer (skips \`uniweb login\`)
1138
+ --dry-run Resolve everything; POST nothing
1126
1139
  `,
1127
1140
  create: `
1128
1141
  ${colors.cyan}${colors.bright}uniweb create${colors.reset} ${colors.dim}— Create a new project${colors.reset}
@@ -1262,8 +1275,9 @@ ${colors.bright}Usage:${colors.reset}
1262
1275
  uniweb register [options]
1263
1276
 
1264
1277
  Builds one \`.uwx\` document and submits it to the registry over HTTP. Run
1265
- \`uniweb login\` first (or pass \`--token\`). Distinct from \`uniweb publish\` (legacy
1266
- hosting platform).
1278
+ \`uniweb login\` first (or pass \`--token\`). \`register\` is for FOUNDATIONS (and
1279
+ schemas); \`uniweb publish\` makes a synced SITE live; \`uniweb deploy\` hosts a
1280
+ file-built site.
1267
1281
 
1268
1282
  Auto-detects what you run it in:
1269
1283
  • a foundation the foundation + the data schemas it defines/renders
@@ -1321,52 +1335,38 @@ ${colors.cyan}${colors.bright}uniweb login${colors.reset} ${colors.dim}— Log i
1321
1335
  ${colors.bright}Usage:${colors.reset}
1322
1336
  uniweb login [options]
1323
1337
 
1324
- Opens a browser to www.uniweb.app for OAuth-style login, then captures
1325
- the token via a loopback callback. Falls back to a paste-token prompt
1326
- if the browser flow fails.
1338
+ Authenticates with the backend and stores a session at
1339
+ ~/.uniweb/registry-auth.json. Every backend command (register, push, pull,
1340
+ clone, deploy) reuses it. Interactively, pick username/password or paste a token.
1327
1341
 
1328
1342
  ${colors.bright}Options:${colors.reset}
1329
- --backend <url> Override the auth backend (default: https://www.uniweb.app)
1330
-
1331
- In non-interactive mode (CI / no TTY / --non-interactive), this command
1332
- errors out — set the \`UNIWEB_TOKEN\` env var instead, or run \`login\`
1333
- once on a machine with a browser to seed ~/.uniweb/auth.json.
1343
+ --backend <url> Backend origin (default: \$UNIWEB_REGISTER_URL or built-in)
1344
+ --token <bearer> Seed + verify a session from a bearer token (non-interactive)
1345
+ --password Force the username/password method
1346
+ --token-paste Force the paste-a-token prompt
1347
+
1348
+ In non-interactive mode (CI / no TTY), pass \`--token <bearer>\`, or set
1349
+ \`UNIWEB_USERNAME\` + \`UNIWEB_PASSWORD\`, or set \`UNIWEB_TOKEN\` (used per-command,
1350
+ not stored). Run \`uniweb logout\` to clear the stored session.
1334
1351
  `,
1335
1352
  invite: `
1336
- ${colors.cyan}${colors.bright}uniweb invite${colors.reset} ${colors.dim}— Create a foundation invite for a client${colors.reset}
1337
-
1338
- ${colors.bright}Usage:${colors.reset}
1339
- uniweb invite <email> [options]
1353
+ ${colors.cyan}${colors.bright}uniweb invite${colors.reset} ${colors.dim}— (reserved; not available on the new backend yet)${colors.reset}
1340
1354
 
1341
- ${colors.bright}Options:${colors.reset}
1342
- --uses <n> Max sites per invite (default: 1)
1343
- --expires <days> Days until expiry (default: 30)
1344
- --version <n> Major version to license (default: current)
1345
- --list List invites for your foundation
1346
- --revoke <id> Revoke an invite
1347
- --resend <id> Resend an invite
1355
+ The foundation client-invite flow was retired with the legacy backend.
1356
+ Invite clients to your foundation from the Uniweb app for now.
1348
1357
  `,
1349
1358
  handoff: `
1350
- ${colors.cyan}${colors.bright}uniweb handoff${colors.reset} ${colors.dim}— Hand off a site to a client${colors.reset}
1351
-
1352
- ${colors.bright}Usage:${colors.reset}
1353
- uniweb handoff <email> [options]
1359
+ ${colors.cyan}${colors.bright}uniweb handoff${colors.reset} ${colors.dim}— (reserved; not available on the new backend yet)${colors.reset}
1354
1360
 
1355
- ${colors.bright}Options:${colors.reset}
1356
- --site <id> Site identifier (default: auto-generated)
1357
- --web Show web-based handoff instructions instead
1361
+ The site-handoff flow was retired with the legacy backend.
1362
+ Manage client sites from the Uniweb app for now.
1358
1363
  `,
1359
1364
  template: `
1360
- ${colors.cyan}${colors.bright}uniweb template${colors.reset} ${colors.dim}— Manage cloud templates${colors.reset}
1361
-
1362
- ${colors.bright}Subcommands:${colors.reset}
1363
- template publish Publish a site as a cloud template
1365
+ ${colors.cyan}${colors.bright}uniweb template${colors.reset} ${colors.dim}— (reserved; not available on the new backend yet)${colors.reset}
1364
1366
 
1365
- ${colors.bright}Publish Options:${colors.reset}
1366
- --name <name> Template registry name (overrides site.yml template: field)
1367
- --title <title> Display title (overrides site.yml name: field)
1368
- --description <t> Description
1369
- --registry <url> Registry URL (default: http://localhost:4001)
1367
+ Submitting a site as a cloud template was retired with the legacy backend.
1368
+ When rebuilt, a template is REGISTERED (like a foundation) — \`publish\` is for
1369
+ sites only. Scaffolding FROM a template still works: \`uniweb create --template <name>\`.
1370
1370
  `,
1371
1371
  docs: `
1372
1372
  ${colors.cyan}${colors.bright}uniweb docs${colors.reset} ${colors.dim}— Generate component documentation${colors.reset}
@@ -1465,20 +1465,19 @@ ${colors.bright}Commands:${colors.reset}
1465
1465
  build Build the current project
1466
1466
  deploy Deploy a site to Uniweb hosting
1467
1467
  export Export a self-contained site for third-party hosting
1468
- publish Publish a foundation to the Uniweb registry
1468
+ publish Publish a synced site (make its backend state live)
1469
1469
  register Register a foundation + its data schemas with the backend registry
1470
+ runtime register Register an @uniweb/runtime version to the backend (@std only)
1470
1471
  push Push a site's content to the backend
1471
1472
  pull Pull a site's content from the backend
1472
- invite <email> Create a foundation invite for a client
1473
- handoff <email> Hand off a site to a client
1474
1473
  inspect <path> Inspect parsed content shape of a markdown file or folder
1475
1474
  docs Generate component documentation
1476
1475
  doctor Diagnose project configuration issues
1477
1476
  validate Check your content against your foundation's data schemas
1478
1477
  update Align workspace deps + AGENTS.md to the running CLI
1479
1478
  i18n <cmd> Internationalization (extract, sync, status)
1480
- template publish Publish a site as a cloud template
1481
1479
  login Log in to your Uniweb account
1480
+ logout Clear the stored session
1482
1481
 
1483
1482
  ${colors.bright}Create Options:${colors.reset}
1484
1483
  --template <type> Project template (default: starter)
@@ -1499,37 +1498,12 @@ ${colors.bright}Global Options:${colors.reset}
1499
1498
  Auto-detected when CI=true or no TTY (pipes, agents)
1500
1499
 
1501
1500
  ${colors.bright}Publish Options:${colors.reset}
1502
- --catalog Confirm publish to the public catalog (required in CI)
1503
- --propagate Walk trusting sites' policy waves (default: silent)
1504
- --name <id> Foundation id (overrides package.json::uniweb.id)
1505
- --namespace <ns> Force org-scope namespace (overrides package.json)
1506
- --local Publish to the local registry (.unicloud/) instead of Uniweb Registry
1507
- --registry <url> Use a specific registry URL
1508
- --edit-access <p> Set edit access policy: "open" or "restricted" (default: restricted)
1509
- --dry-run Show what would be published without uploading
1510
-
1511
- uniweb publish is for cataloging a foundation as a product. For
1512
- site-bound foundations (one foundation, one site), use uniweb deploy
1513
- instead — it auto-publishes under a site-scoped slot, no naming
1514
- ceremony.
1515
-
1516
- ${colors.bright}Invite Options:${colors.reset}
1517
- --uses <n> Max sites per invite (default: 1)
1518
- --expires <days> Days until expiry (default: 30)
1519
- --version <n> Major version to license (default: current)
1520
- --list List invites for your foundation
1521
- --revoke <id> Revoke an invite
1522
- --resend <id> Resend an invite
1523
-
1524
- ${colors.bright}Handoff Options:${colors.reset}
1525
- --site <id> Site identifier (default: auto-generated)
1526
- --web Show web-based handoff instructions instead
1527
-
1528
- ${colors.bright}Template Options:${colors.reset}
1529
- --name <name> Template registry name (overrides site.yml template: field)
1530
- --title <title> Display title (overrides site.yml name: field)
1531
- --description <t> Description
1532
- --registry <url> Registry URL (default: http://localhost:4001)
1501
+ --backend <url> Backend origin (default: \$UNIWEB_REGISTER_URL or built-in)
1502
+ --token <bearer> Auth bearer (skips \`uniweb login\`)
1503
+ --dry-run Resolve everything; POST nothing
1504
+
1505
+ uniweb publish makes a SYNCED site live (run \`uniweb push\` first). To register
1506
+ a foundation use \`uniweb register\`; to host a file-built site use \`uniweb deploy\`.
1533
1507
 
1534
1508
  ${colors.bright}Deploy Options:${colors.reset}
1535
1509
  --target <name> Pick a target from deploy.yml (default: deploy.yml's \`default:\`)