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,254 +1,17 @@
1
1
  /**
2
- * Handoff Command
2
+ * handoff — RESERVED (not available on the new backend yet).
3
3
  *
4
- * Creates a site record on Unicloud and transfers ownership to a client.
5
- * The developer builds content locally and hands off a licensed, registered site.
6
- *
7
- * Usage:
8
- * uniweb handoff <email> # Register site + transfer to client
9
- * uniweb handoff <email> --site <id> # Specify site ID (default: auto-generated)
10
- * uniweb handoff <email> --web # Show web-based handoff instructions
11
- */
12
-
13
- import { existsSync } from 'node:fs'
14
- import { readFile } from 'node:fs/promises'
15
- import { resolve, join } from 'node:path'
16
- import { execSync } from 'node:child_process'
17
- import { randomUUID } from 'node:crypto'
18
-
19
- import { RemoteRegistry } from '../utils/registry.js'
20
- import { ensureAuth } from '../utils/auth.js'
21
- import { findWorkspaceRoot, findFoundations, classifyPackage, promptSelect } from '../utils/workspace.js'
22
- import { isNonInteractive, getCliPrefix } from '../utils/interactive.js'
23
-
24
- const colors = {
25
- reset: '\x1b[0m',
26
- bright: '\x1b[1m',
27
- dim: '\x1b[2m',
28
- cyan: '\x1b[36m',
29
- green: '\x1b[32m',
30
- yellow: '\x1b[33m',
31
- red: '\x1b[31m',
32
- }
33
-
34
- function success(message) {
35
- console.log(`${colors.green}✓${colors.reset} ${message}`)
36
- }
37
-
38
- function error(message) {
39
- console.error(`${colors.red}✗${colors.reset} ${message}`)
40
- }
41
-
42
- function info(message) {
43
- console.log(`${colors.cyan}→${colors.reset} ${message}`)
44
- }
45
-
46
- /**
47
- * Parse a flag value from args.
48
- * @param {string[]} args
49
- * @param {string} flag - e.g. '--site'
50
- * @returns {string|null}
4
+ * The "create a site and hand it off to a client" flow ran against the legacy
5
+ * platform (the PHP backend + Cloudflare Worker) the CLI no longer talks to. A
6
+ * new-backend CLI equivalent isn't built; the command name is kept reserved so a
7
+ * future rebuild can claim it without a breaking change.
51
8
  */
52
- function parseFlag(args, flag) {
53
- const idx = args.indexOf(flag)
54
- if (idx === -1 || !args[idx + 1]) return null
55
- return args[idx + 1]
56
- }
57
-
58
- /**
59
- * Resolve the foundation directory (same logic as invite.js).
60
- */
61
- async function resolveFoundationDir(args) {
62
- const cwd = process.cwd()
63
- const prefix = getCliPrefix()
64
-
65
- const type = await classifyPackage(cwd)
66
- if (type === 'foundation') return cwd
67
-
68
- const workspaceRoot = findWorkspaceRoot(cwd)
69
- if (workspaceRoot) {
70
- const foundations = await findFoundations(workspaceRoot)
71
-
72
- if (foundations.length === 1) {
73
- return resolve(workspaceRoot, foundations[0])
74
- }
75
9
 
76
- if (foundations.length > 1) {
77
- if (isNonInteractive(args)) {
78
- error('Multiple foundations found. Specify which one.')
79
- console.log('')
80
- for (const f of foundations) {
81
- console.log(` ${colors.cyan}cd ${f} && ${prefix} handoff ...${colors.reset}`)
82
- }
83
- process.exit(1)
84
- }
85
-
86
- const choice = await promptSelect('Which foundation?', foundations)
87
- if (!choice) {
88
- console.log('\nCancelled.')
89
- process.exit(0)
90
- }
91
- return resolve(workspaceRoot, choice)
92
- }
93
- }
94
-
95
- error('No foundation found in this workspace.')
96
- console.log('')
97
- console.log(` ${colors.dim}\`handoff\` creates a site record for your foundation and transfers${colors.reset}`)
98
- console.log(` ${colors.dim}ownership to a client — they get a licensed, ready-to-use project.${colors.reset}`)
99
- console.log('')
100
- console.log(` ${colors.dim}Run this command from a foundation directory or workspace root.${colors.reset}`)
10
+ export async function handoff() {
11
+ console.error("\x1b[31m✗\x1b[0m `uniweb handoff` isn't available on the new backend yet.")
12
+ console.error(' The legacy site-handoff flow was retired with the PHP backend.')
13
+ console.error(' Manage client sites from the Uniweb app for now.')
101
14
  process.exit(1)
102
15
  }
103
16
 
104
- /**
105
- * Read foundation name and version from dist/meta/schema.json, auto-building if needed.
106
- */
107
- async function readSchema(foundationDir) {
108
- const distDir = join(foundationDir, 'dist')
109
- const foundationJs = join(distDir, 'foundation.js')
110
- const schemaJson = join(distDir, 'meta', 'schema.json')
111
-
112
- if (!existsSync(foundationJs) || !existsSync(schemaJson)) {
113
- console.log(`${colors.yellow}⚠${colors.reset} No build found. Building foundation...`)
114
- console.log('')
115
- execSync('npx uniweb build --target foundation', {
116
- cwd: foundationDir,
117
- stdio: 'inherit',
118
- })
119
- console.log('')
120
-
121
- if (!existsSync(foundationJs) || !existsSync(schemaJson)) {
122
- error('Build did not produce dist/foundation.js and dist/meta/schema.json')
123
- process.exit(1)
124
- }
125
- }
126
-
127
- try {
128
- const schema = JSON.parse(await readFile(schemaJson, 'utf8'))
129
- const name = schema._self?.name
130
- const version = schema._self?.version
131
-
132
- if (!name || !version) {
133
- error('dist/meta/schema.json missing _self.name or _self.version')
134
- process.exit(1)
135
- }
136
-
137
- return { name, version }
138
- } catch (err) {
139
- error(`Failed to read dist/meta/schema.json: ${err.message}`)
140
- process.exit(1)
141
- }
142
- }
143
-
144
- /**
145
- * Create a RemoteRegistry instance with auth.
146
- */
147
- async function createRegistry(args) {
148
- const token = await ensureAuth({ command: 'Handing off', args })
149
-
150
- const registryUrl = parseFlag(args, '--registry')
151
- const url = registryUrl || process.env.UNIWEB_REGISTRY_URL || 'http://localhost:4001'
152
-
153
- return new RemoteRegistry(url, token)
154
- }
155
-
156
- /**
157
- * Handle --web flag: show web-based handoff guidance.
158
- */
159
- function showWebHandoff(email, name) {
160
- console.log('')
161
- info(`Web-based handoff`)
162
- console.log('')
163
- console.log(` 1. Create a site on ${colors.cyan}uniweb.app${colors.reset} using ${colors.bright}${name}${colors.reset}`)
164
- console.log(` 2. Add pages and content`)
165
- console.log(` 3. Transfer ownership to ${colors.bright}${email}${colors.reset}:`)
166
- console.log(` ${colors.dim}Settings → Transfer site${colors.reset}`)
167
- console.log('')
168
- console.log(` ${colors.dim}The license stays with the site — the client can edit${colors.reset}`)
169
- console.log(` ${colors.dim}and publish immediately.${colors.reset}`)
170
- }
171
-
172
- /**
173
- * Main handoff command handler.
174
- */
175
- export async function handoff(args = []) {
176
- const prefix = getCliPrefix()
177
-
178
- // Extract email (first positional arg with @)
179
- const email = args.find(a => !a.startsWith('--') && a.includes('@'))
180
- if (!email) {
181
- error('Email is required.')
182
- console.log('')
183
- console.log(` ${colors.dim}Usage: ${prefix} handoff <email> [--site <id>] [--web]${colors.reset}`)
184
- console.log('')
185
- console.log(` ${colors.dim}Creates a site record for your foundation and transfers ownership${colors.reset}`)
186
- console.log(` ${colors.dim}to the client. They get a licensed project, ready to use.${colors.reset}`)
187
- process.exit(1)
188
- }
189
-
190
- const foundationDir = await resolveFoundationDir(args)
191
- const { name, version } = await readSchema(foundationDir)
192
-
193
- // --web: guidance-only, no API call
194
- if (args.includes('--web')) {
195
- showWebHandoff(email, name)
196
- return
197
- }
198
-
199
- const registry = await createRegistry(args)
200
-
201
- // Derive site ID
202
- const siteIdFlag = parseFlag(args, '--site')
203
- const siteId = siteIdFlag || `${name}-${randomUUID().slice(0, 6)}`
204
-
205
- info(`Creating site ${colors.bright}${siteId}${colors.reset} with ${name} v${version}...`)
206
-
207
- // 1. Create site record
208
- let siteResult
209
- try {
210
- siteResult = await registry.createSite(siteId, { foundation: { name } })
211
- } catch (err) {
212
- if (err.statusCode === 409) {
213
- error(`Site "${siteId}" already exists.`)
214
- console.log(` ${colors.dim}Use --site <id> to specify a different site identifier.${colors.reset}`)
215
- process.exit(1)
216
- }
217
- if (err.statusCode === 404) {
218
- error(err.message)
219
- console.log(` ${colors.dim}Make sure your foundation is published: ${prefix} publish${colors.reset}`)
220
- process.exit(1)
221
- }
222
- error(err.message)
223
- process.exit(1)
224
- }
225
-
226
- // 2. Transfer ownership to client
227
- info(`Transferring to ${colors.bright}${email}${colors.reset}...`)
228
-
229
- try {
230
- await registry.transferSiteOwnership(siteId, email)
231
- } catch (err) {
232
- error(`Site created but transfer failed: ${err.message}`)
233
- console.log(` ${colors.dim}Site "${siteId}" is registered. Transfer manually:${colors.reset}`)
234
- console.log(` ${colors.dim} PATCH /api/sites/${siteId}/owner { "newOwner": "${email}" }${colors.reset}`)
235
- process.exit(1)
236
- }
237
-
238
- // 3. Show result
239
- console.log('')
240
- success(`Site created and transferred`)
241
- console.log('')
242
- console.log(` ${colors.dim}Site:${colors.reset} ${colors.bright}${siteId}${colors.reset}`)
243
- console.log(` ${colors.dim}Foundation:${colors.reset} ${name} v${version.split('.')[0]}`)
244
- console.log(` ${colors.dim}Owner:${colors.reset} ${email}`)
245
- console.log(` ${colors.dim}License:${colors.reset} ${siteResult.license ? `${colors.green}✓${colors.reset} granted` : `${colors.yellow}⚠${colors.reset} not granted`}`)
246
- console.log('')
247
- console.log(` ${colors.bright}Next steps:${colors.reset}`)
248
- console.log(` 1. Add ${colors.cyan}id: ${siteId}${colors.reset} to your site.yml`)
249
- console.log(` 2. Share the site files with ${colors.bright}${email}${colors.reset}`)
250
- console.log(` ${colors.dim}(git repo, zip, shared drive — any method works)${colors.reset}`)
251
- console.log(` 3. Client opens the project in Uniweb Studio`)
252
- }
253
-
254
17
  export default handoff
@@ -1,326 +1,18 @@
1
1
  /**
2
- * Invite Command
2
+ * invite — RESERVED (not available on the new backend yet).
3
3
  *
4
- * Creates, lists, revokes, and resends foundation invites.
5
- *
6
- * Usage:
7
- * uniweb invite <email> # Create invite (1 use, 30 days)
8
- * uniweb invite <email> --uses 5 # Multi-use invite
9
- * uniweb invite <email> --expires 60 # 60-day expiry
10
- * uniweb invite --list # List invites for your foundation
11
- * uniweb invite --revoke <inviteId> # Revoke an invite
12
- * uniweb invite --resend <inviteId> # Resend an invite
13
- */
14
-
15
- import { existsSync } from 'node:fs'
16
- import { readFile } from 'node:fs/promises'
17
- import { resolve, join } from 'node:path'
18
- import { execSync } from 'node:child_process'
19
-
20
- import { RemoteRegistry } from '../utils/registry.js'
21
- import { ensureAuth } from '../utils/auth.js'
22
- import { findWorkspaceRoot, findFoundations, classifyPackage, promptSelect } from '../utils/workspace.js'
23
- import { isNonInteractive, getCliPrefix } from '../utils/interactive.js'
24
-
25
- const colors = {
26
- reset: '\x1b[0m',
27
- bright: '\x1b[1m',
28
- dim: '\x1b[2m',
29
- cyan: '\x1b[36m',
30
- green: '\x1b[32m',
31
- yellow: '\x1b[33m',
32
- red: '\x1b[31m',
33
- }
34
-
35
- function success(message) {
36
- console.log(`${colors.green}✓${colors.reset} ${message}`)
37
- }
38
-
39
- function error(message) {
40
- console.error(`${colors.red}✗${colors.reset} ${message}`)
41
- }
42
-
43
- function info(message) {
44
- console.log(`${colors.cyan}→${colors.reset} ${message}`)
45
- }
46
-
47
- /**
48
- * Parse a flag value from args.
49
- * @param {string[]} args
50
- * @param {string} flag - e.g. '--uses'
51
- * @returns {string|null}
52
- */
53
- function parseFlag(args, flag) {
54
- const idx = args.indexOf(flag)
55
- if (idx === -1 || !args[idx + 1]) return null
56
- return args[idx + 1]
57
- }
58
-
59
- /**
60
- * Resolve the foundation directory (same logic as publish.js).
4
+ * The foundation client-invite flow ran against the legacy platform (the PHP
5
+ * backend + Cloudflare Worker) the CLI no longer talks to. Client onboarding is
6
+ * handled in the Uniweb app for now; a new-backend CLI equivalent isn't built.
7
+ * The command name is kept reserved so a future rebuild can claim it without a
8
+ * breaking change.
61
9
  */
62
- async function resolveFoundationDir(args) {
63
- const cwd = process.cwd()
64
- const prefix = getCliPrefix()
65
-
66
- const type = await classifyPackage(cwd)
67
- if (type === 'foundation') return cwd
68
-
69
- const workspaceRoot = findWorkspaceRoot(cwd)
70
- if (workspaceRoot) {
71
- const foundations = await findFoundations(workspaceRoot)
72
10
 
73
- if (foundations.length === 1) {
74
- return resolve(workspaceRoot, foundations[0])
75
- }
76
-
77
- if (foundations.length > 1) {
78
- if (isNonInteractive(args)) {
79
- error('Multiple foundations found. Specify which one.')
80
- console.log('')
81
- for (const f of foundations) {
82
- console.log(` ${colors.cyan}cd ${f} && ${prefix} invite ...${colors.reset}`)
83
- }
84
- process.exit(1)
85
- }
86
-
87
- const choice = await promptSelect('Which foundation?', foundations)
88
- if (!choice) {
89
- console.log('\nCancelled.')
90
- process.exit(0)
91
- }
92
- return resolve(workspaceRoot, choice)
93
- }
94
- }
95
-
96
- error('No foundation found in this workspace.')
97
- console.log('')
98
- console.log(` ${colors.dim}\`invite\` lets you authorize a client to create sites with your${colors.reset}`)
99
- console.log(` ${colors.dim}foundation — they get their own site, set up automatically.${colors.reset}`)
100
- console.log('')
101
- console.log(` ${colors.dim}Run this command from a foundation directory or workspace root.${colors.reset}`)
11
+ export async function invite() {
12
+ console.error("\x1b[31m✗\x1b[0m `uniweb invite` isn't available on the new backend yet.")
13
+ console.error(' The legacy client-invite flow was retired with the PHP backend.')
14
+ console.error(' Invite clients to your foundation from the Uniweb app for now.')
102
15
  process.exit(1)
103
16
  }
104
17
 
105
- /**
106
- * Read foundation name and version from dist/meta/schema.json, auto-building if needed.
107
- */
108
- async function readSchema(foundationDir) {
109
- const distDir = join(foundationDir, 'dist')
110
- const foundationJs = join(distDir, 'foundation.js')
111
- const schemaJson = join(distDir, 'meta', 'schema.json')
112
-
113
- if (!existsSync(foundationJs) || !existsSync(schemaJson)) {
114
- console.log(`${colors.yellow}⚠${colors.reset} No build found. Building foundation...`)
115
- console.log('')
116
- execSync('npx uniweb build --target foundation', {
117
- cwd: foundationDir,
118
- stdio: 'inherit',
119
- })
120
- console.log('')
121
-
122
- if (!existsSync(foundationJs) || !existsSync(schemaJson)) {
123
- error('Build did not produce dist/foundation.js and dist/meta/schema.json')
124
- process.exit(1)
125
- }
126
- }
127
-
128
- try {
129
- const schema = JSON.parse(await readFile(schemaJson, 'utf8'))
130
- const name = schema._self?.name
131
- const version = schema._self?.version
132
-
133
- if (!name || !version) {
134
- error('dist/meta/schema.json missing _self.name or _self.version')
135
- process.exit(1)
136
- }
137
-
138
- return { name, version, role: schema._self?.role }
139
- } catch (err) {
140
- error(`Failed to read dist/meta/schema.json: ${err.message}`)
141
- process.exit(1)
142
- }
143
- }
144
-
145
- /**
146
- * Create a RemoteRegistry instance with auth.
147
- */
148
- async function createRegistry(args) {
149
- const token = await ensureAuth({ command: 'Creating invite', args })
150
-
151
- const registryUrl = parseFlag(args, '--registry')
152
- const url = registryUrl || process.env.UNIWEB_REGISTRY_URL || 'http://localhost:4001'
153
-
154
- return new RemoteRegistry(url, token)
155
- }
156
-
157
- /**
158
- * Handle --list flag.
159
- */
160
- async function handleList(args) {
161
- const foundationDir = await resolveFoundationDir(args)
162
- const { name } = await readSchema(foundationDir)
163
- const registry = await createRegistry(args)
164
-
165
- info(`Listing invites for ${colors.bright}${name}${colors.reset}...`)
166
-
167
- try {
168
- const invites = await registry.listInvites(name)
169
-
170
- if (invites.length === 0) {
171
- console.log(`\n No invites found for ${name}.`)
172
- return
173
- }
174
-
175
- console.log('')
176
- for (const inv of invites) {
177
- const statusColor = inv.status === 'active' ? colors.green
178
- : inv.status === 'revoked' ? colors.red
179
- : colors.yellow
180
- console.log(` ${statusColor}${inv.status}${colors.reset} ${inv.email} v${inv.majorVersion} ${inv.usedCount}/${inv.maxUses} uses ${colors.dim}${inv.inviteId}${colors.reset}`)
181
- }
182
- console.log('')
183
- } catch (err) {
184
- error(err.message)
185
- process.exit(1)
186
- }
187
- }
188
-
189
- /**
190
- * Handle --revoke flag.
191
- */
192
- async function handleRevoke(args, inviteId) {
193
- const foundationDir = await resolveFoundationDir(args)
194
- const { name } = await readSchema(foundationDir)
195
- const registry = await createRegistry(args)
196
-
197
- info(`Revoking invite ${colors.dim}${inviteId}${colors.reset}...`)
198
-
199
- try {
200
- const result = await registry.revokeInvite(name, inviteId)
201
- console.log('')
202
- success(`Revoked invite for ${colors.bright}${result.email}${colors.reset}`)
203
- } catch (err) {
204
- error(err.message)
205
- process.exit(1)
206
- }
207
- }
208
-
209
- /**
210
- * Handle --resend flag.
211
- */
212
- async function handleResend(args, inviteId) {
213
- const foundationDir = await resolveFoundationDir(args)
214
- const { name } = await readSchema(foundationDir)
215
- const registry = await createRegistry(args)
216
-
217
- info(`Resending invite ${colors.dim}${inviteId}${colors.reset}...`)
218
-
219
- try {
220
- const result = await registry.resendInvite(name, inviteId)
221
- console.log('')
222
- success(`Resent invite to ${colors.bright}${result.email}${colors.reset}`)
223
- } catch (err) {
224
- error(err.message)
225
- process.exit(1)
226
- }
227
- }
228
-
229
- /**
230
- * Handle create invite (default action).
231
- */
232
- async function handleCreate(args, email) {
233
- const foundationDir = await resolveFoundationDir(args)
234
- const { name, version, role } = await readSchema(foundationDir)
235
- const registry = await createRegistry(args)
236
-
237
- // Parse options
238
- const versionFlag = parseFlag(args, '--version')
239
- const majorVersion = versionFlag
240
- ? parseInt(versionFlag, 10)
241
- : parseInt(version.split('.')[0], 10)
242
-
243
- const usesFlag = parseFlag(args, '--uses')
244
- const maxUses = usesFlag ? parseInt(usesFlag, 10) : 1
245
-
246
- const expiresFlag = parseFlag(args, '--expires')
247
- const expiresInDays = expiresFlag ? parseInt(expiresFlag, 10) : 30
248
-
249
- if (isNaN(majorVersion)) {
250
- error('Could not determine major version. Use --version to specify.')
251
- process.exit(1)
252
- }
253
-
254
- info(`Creating invite for ${colors.bright}${email}${colors.reset} → ${name} v${majorVersion}...`)
255
-
256
- try {
257
- const invite = await registry.createInvite(name, {
258
- email,
259
- majorVersion,
260
- maxUses,
261
- expiresInDays,
262
- })
263
-
264
- const isExtension = role === 'extension'
265
-
266
- console.log('')
267
- success(`Invite created`)
268
- console.log('')
269
- console.log(` ${colors.dim}ID:${colors.reset} ${invite.inviteId}`)
270
- console.log(` ${colors.dim}To:${colors.reset} ${invite.email}`)
271
- console.log(` ${colors.dim}For:${colors.reset} ${name} v${invite.majorVersion}${isExtension ? ' (extension)' : ''}`)
272
- console.log(` ${colors.dim}Uses:${colors.reset} ${invite.maxUses}`)
273
- console.log(` ${colors.dim}Expires:${colors.reset} ${new Date(invite.expiresAt).toLocaleDateString()}`)
274
- console.log(` ${colors.dim}Link:${colors.reset} ${colors.cyan}${registry.apiUrl}/invite/${invite.inviteId}${colors.reset}`)
275
- console.log('')
276
- if (isExtension) {
277
- console.log(` ${colors.dim}When ${invite.email} adds ${name} to their site${colors.reset}`)
278
- console.log(` ${colors.dim}on uniweb.app or Studio, it will be authorized automatically.${colors.reset}`)
279
- } else {
280
- console.log(` ${colors.dim}When ${invite.email} creates a site with ${name}${colors.reset}`)
281
- console.log(` ${colors.dim}on uniweb.app or Studio, it will be authorized automatically.${colors.reset}`)
282
- }
283
- } catch (err) {
284
- error(err.message)
285
- process.exit(1)
286
- }
287
- }
288
-
289
- /**
290
- * Main invite command handler.
291
- */
292
- export async function invite(args = []) {
293
- // Dispatch based on flags
294
- if (args.includes('--list')) {
295
- await handleList(args)
296
- return
297
- }
298
-
299
- const revokeId = parseFlag(args, '--revoke')
300
- if (revokeId) {
301
- await handleRevoke(args, revokeId)
302
- return
303
- }
304
-
305
- const resendId = parseFlag(args, '--resend')
306
- if (resendId) {
307
- await handleResend(args, resendId)
308
- return
309
- }
310
-
311
- // Default: create invite — first positional arg is the email
312
- const email = args.find(a => !a.startsWith('--') && a.includes('@'))
313
- if (!email) {
314
- error('Email is required.')
315
- console.log('')
316
- console.log(` ${colors.dim}Usage: ${getCliPrefix()} invite <email> [--uses N] [--expires N]${colors.reset}`)
317
- console.log(` ${colors.dim} ${getCliPrefix()} invite --list${colors.reset}`)
318
- console.log(` ${colors.dim} ${getCliPrefix()} invite --revoke <id>${colors.reset}`)
319
- console.log(` ${colors.dim} ${getCliPrefix()} invite --resend <id>${colors.reset}`)
320
- process.exit(1)
321
- }
322
-
323
- await handleCreate(args, email)
324
- }
325
-
326
18
  export default invite
@@ -8,9 +8,8 @@
8
8
  * the legacy platform (publish/deploy); this talks to the registry backend.
9
9
  */
10
10
 
11
- import { getRegistryApiBaseUrl } from '../utils/config.js'
12
- import { ensureRegistryAuth } from '../utils/registry-auth.js'
13
- import { listOrgs, createOrg, validateHandle, bareHandle } from '../utils/registry-orgs.js'
11
+ import { BackendClient } from '../backend/client.js'
12
+ import { validateHandle, bareHandle } from '../utils/registry-orgs.js'
14
13
 
15
14
  const colors = { reset: '\x1b[0m', bright: '\x1b[1m', dim: '\x1b[2m', red: '\x1b[31m', green: '\x1b[32m' }
16
15
  const error = (m) => console.error(`${colors.red}✗${colors.reset} ${m}`)
@@ -18,11 +17,10 @@ const success = (m) => console.log(`${colors.green}✓${colors.reset} ${m}`)
18
17
 
19
18
  export async function org(args = []) {
20
19
  const sub = args[0]
21
- const apiBase = getRegistryApiBaseUrl()
22
20
 
23
21
  if (sub === 'list') {
24
- const token = await ensureRegistryAuth({ apiBase, command: 'Listing orgs', args })
25
- const orgs = await listOrgs({ apiBase, token })
22
+ const client = new BackendClient({ args, command: 'Listing orgs' })
23
+ const { orgs } = await client.fetchOrgs()
26
24
  if (!orgs.length) {
27
25
  console.log('You have no orgs yet. Create one: uniweb org create <handle>')
28
26
  return { exitCode: 0 }
@@ -45,9 +43,9 @@ export async function org(args = []) {
45
43
  error(invalid)
46
44
  return { exitCode: 2 }
47
45
  }
48
- const token = await ensureRegistryAuth({ apiBase, command: 'Creating an org', args })
46
+ const client = new BackendClient({ args, command: 'Creating an org' })
49
47
  try {
50
- const org = await createOrg({ apiBase, token, handle })
48
+ const org = await client.createOrg(handle)
51
49
  success(`Created ${colors.bright}@${org.handle}${colors.reset} — you're a member${org.is_primary ? ' (primary)' : ''}.`)
52
50
  console.log(`${colors.dim}Publish under it: uniweb register --scope @${org.handle}${colors.reset}`)
53
51
  return { exitCode: 0 }