···314314}
315315316316/**
317317+ * Lists all packages that a user has access to publish.
318318+ * Uses `npm access list packages @{user} --json`
319319+ * Returns a map of package name to permission level
320320+ */
321321+export async function listUserPackages(user: string): Promise<NpmExecResult> {
322322+ validateUsername(user)
323323+ return execNpm(['access', 'list', 'packages', `@${user}`, '--json'], { silent: true })
324324+}
325325+326326+/**
317327 * Initialize and publish a new package to claim the name.
318328 * Creates a minimal package.json in a temp directory and publishes it.
319329 * @param name Package name to claim
+109-4
cli/src/server.ts
···2323 ownerAdd,
2424 ownerRemove,
2525 packageInit,
2626+ listUserPackages,
2627 type NpmExecResult,
2728} from './npm-client.ts'
2829import {
···234235 }
235236236237 if (operation.status !== 'pending') {
237237- throw new HTTPError({ statusCode: 400, message: 'Operation is not pending' })
238238+ throw new HTTPError({
239239+ statusCode: 400,
240240+ message: 'Operation is not pending',
241241+ })
238242 }
239243240244 operation.status = 'approved'
···282286 }
283287284288 if (operation.status !== 'failed') {
285285- throw new HTTPError({ statusCode: 400, message: 'Only failed operations can be retried' })
289289+ throw new HTTPError({
290290+ statusCode: 400,
291291+ message: 'Only failed operations can be retried',
292292+ })
286293 }
287294288295 // Reset the operation for retry
···337344 // Dependency failed - skip this one too
338345 if (failedIds.has(op.dependsOn)) {
339346 op.status = 'failed'
340340- op.result = { stdout: '', stderr: 'Skipped: dependency failed', exitCode: 1 }
347347+ op.result = {
348348+ stdout: '',
349349+ stderr: 'Skipped: dependency failed',
350350+ exitCode: 1,
351351+ }
341352 failedIds.add(op.id)
342353 results.push({ id: op.id, result: op.result })
343354 return false
···410421411422 const operation = state.operations[index]
412423 if (!operation || operation.status === 'running') {
413413- throw new HTTPError({ statusCode: 400, message: 'Cannot cancel running operation' })
424424+ throw new HTTPError({
425425+ statusCode: 400,
426426+ message: 'Cannot cancel running operation',
427427+ })
414428 }
415429416430 state.operations.splice(index, 1)
···586600 return {
587601 success: false,
588602 error: 'Failed to parse collaborators',
603603+ } as ApiResponse
604604+ }
605605+ })
606606+607607+ // User-specific endpoints
608608+609609+ app.get('/user/packages', async event => {
610610+ const auth = event.req.headers.get('authorization')
611611+ if (!validateToken(auth)) {
612612+ throw new HTTPError({ statusCode: 401, message: 'Unauthorized' })
613613+ }
614614+615615+ const npmUser = state.session.npmUser
616616+ if (!npmUser) {
617617+ return {
618618+ success: false,
619619+ error: 'Not logged in to npm',
620620+ } as ApiResponse
621621+ }
622622+623623+ const result = await listUserPackages(npmUser)
624624+ if (result.exitCode !== 0) {
625625+ return {
626626+ success: false,
627627+ error: result.stderr || 'Failed to list user packages',
628628+ } as ApiResponse
629629+ }
630630+631631+ try {
632632+ // npm access list packages returns { "packageName": "read-write" | "read-only" }
633633+ const packages = JSON.parse(result.stdout) as Record<string, 'read-write' | 'read-only'>
634634+ return {
635635+ success: true,
636636+ data: packages,
637637+ } as ApiResponse
638638+ } catch {
639639+ return {
640640+ success: false,
641641+ error: 'Failed to parse user packages',
642642+ } as ApiResponse
643643+ }
644644+ })
645645+646646+ app.get('/user/orgs', async event => {
647647+ const auth = event.req.headers.get('authorization')
648648+ if (!validateToken(auth)) {
649649+ throw new HTTPError({ statusCode: 401, message: 'Unauthorized' })
650650+ }
651651+652652+ const npmUser = state.session.npmUser
653653+ if (!npmUser) {
654654+ return {
655655+ success: false,
656656+ error: 'Not logged in to npm',
657657+ } as ApiResponse
658658+ }
659659+660660+ // Get user's packages and extract org names from scoped packages
661661+ const result = await listUserPackages(npmUser)
662662+ if (result.exitCode !== 0) {
663663+ return {
664664+ success: false,
665665+ error: result.stderr || 'Failed to list user packages',
666666+ } as ApiResponse
667667+ }
668668+669669+ try {
670670+ const packages = JSON.parse(result.stdout) as Record<string, string>
671671+ const orgs = new Set<string>()
672672+673673+ // Extract org names from scoped packages (e.g., @myorg/mypackage -> myorg)
674674+ for (const pkgName of Object.keys(packages)) {
675675+ if (pkgName.startsWith('@')) {
676676+ const match = pkgName.match(/^@([^/]+)\//)
677677+ if (match && match[1]) {
678678+ // Exclude the user's own scope (personal packages)
679679+ if (match[1].toLowerCase() !== npmUser.toLowerCase()) {
680680+ orgs.add(match[1])
681681+ }
682682+ }
683683+ }
684684+ }
685685+686686+ return {
687687+ success: true,
688688+ data: Array.from(orgs).sort(),
689689+ } as ApiResponse
690690+ } catch {
691691+ return {
692692+ success: false,
693693+ error: 'Failed to parse user orgs',
589694 } as ApiResponse
590695 }
591696 })