Skip to content

Commit 2e9c300

Browse files
committed
fix: allow users to select which publisher to publish from
1 parent 7465aa5 commit 2e9c300

File tree

4 files changed

+183
-23
lines changed

4 files changed

+183
-23
lines changed

npm-app/src/cli-definitions.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,4 +93,10 @@ export const cliOptions: CliParam[] = [
9393
menuDescription: 'Log subagent messages to trace files',
9494
hidden: false,
9595
},
96+
{
97+
flags: '--publisher <publisher-id>',
98+
description: 'Specify which publisher to use when publishing agents',
99+
menuDescription: 'Specify publisher for agent publishing',
100+
hidden: false,
101+
},
96102
]

npm-app/src/cli-handlers/publish.ts

Lines changed: 86 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,12 @@ interface PublishResponse {
1717
error?: string
1818
details?: string
1919
statusCode?: number
20+
availablePublishers?: Array<{
21+
id: string
22+
name: string
23+
ownershipType: 'user' | 'organization'
24+
organizationName?: string
25+
}>
2026
validationErrors?: Array<{
2127
code: string
2228
message: string
@@ -27,7 +33,11 @@ interface PublishResponse {
2733
/**
2834
* Handle the publish command to upload agent templates to the backend
2935
* @param agentId The id of the agent to publish (required)
30-
*/ export async function handlePublish(agentId?: string): Promise<void> {
36+
* @param publisherId The id of the publisher to use (optional)
37+
*/ export async function handlePublish(
38+
agentId?: string,
39+
publisherId?: string,
40+
): Promise<void> {
3141
const user = getUserCredentials()
3242

3343
if (!user) {
@@ -95,7 +105,11 @@ interface PublishResponse {
95105
)
96106

97107
try {
98-
const result = await publishAgentTemplate(template, user.authToken!)
108+
const result = await publishAgentTemplate(
109+
template,
110+
user.authToken!,
111+
publisherId,
112+
)
99113

100114
if (result.success) {
101115
console.log(
@@ -111,16 +125,73 @@ interface PublishResponse {
111125
// Check if the error is about missing publisher (403 status)
112126
if (result.statusCode === 403) {
113127
console.log()
114-
console.log(
115-
cyan('Please visit the website to create your publisher profile:'),
116-
)
117-
console.log(yellow(`${websiteUrl}/publishers`))
118-
console.log()
119-
console.log('A publisher profile allows you to:')
120-
console.log(' • Publish and manage your agents')
121-
console.log(' • Build your reputation in the community')
122-
console.log(' • Organize agents under your name or organization')
123-
console.log()
128+
129+
// Check if this is a "no publisher" error vs "multiple publishers" error
130+
if (result.error?.includes('No publisher associated with user')) {
131+
console.log(
132+
cyan(
133+
'Please visit the website to create your publisher profile:',
134+
),
135+
)
136+
console.log(yellow(`${websiteUrl}/publishers`))
137+
console.log()
138+
console.log('A publisher profile allows you to:')
139+
console.log(' • Publish and manage your agents')
140+
console.log(' • Build your reputation in the community')
141+
console.log(' • Organize agents under your name or organization')
142+
console.log()
143+
} else if (
144+
result.availablePublishers &&
145+
result.availablePublishers.length > 0
146+
) {
147+
// Show available publishers
148+
console.log(
149+
cyan(
150+
'You have access to multiple publishers. Please specify which one to use:',
151+
),
152+
)
153+
console.log()
154+
console.log(cyan('Available publishers:'))
155+
result.availablePublishers.forEach((publisher) => {
156+
const orgInfo = publisher.organizationName
157+
? ` (${publisher.organizationName})`
158+
: ''
159+
const typeInfo =
160+
publisher.ownershipType === 'organization'
161+
? ' [Organization]'
162+
: ' [Personal]'
163+
console.log(
164+
` • ${yellow(publisher.id)} - ${publisher.name}${orgInfo}${typeInfo}`,
165+
)
166+
})
167+
console.log()
168+
console.log('Run one of these commands:')
169+
result.availablePublishers.forEach((publisher) => {
170+
console.log(
171+
yellow(
172+
` codebuff publish ${agentId} --publisher ${publisher.id}`,
173+
),
174+
)
175+
})
176+
console.log()
177+
console.log(cyan('Or visit the website to manage your publishers:'))
178+
console.log(yellow(`${websiteUrl}/publishers`))
179+
console.log()
180+
} else {
181+
// Generic 403 error
182+
console.log(cyan('You may need to specify which publisher to use.'))
183+
console.log()
184+
console.log('Try running:')
185+
console.log(
186+
yellow(` publish ${agentId} --publisher <publisher-id>`),
187+
)
188+
console.log()
189+
console.log(
190+
cyan('Visit the website to see your available publishers:'),
191+
)
192+
console.log(yellow(`${websiteUrl}/publishers`))
193+
console.log()
194+
}
124195
} else {
125196
console.log(
126197
red(
@@ -155,6 +226,7 @@ interface PublishResponse {
155226
async function publishAgentTemplate(
156227
data: DynamicAgentTemplate,
157228
authToken: string,
229+
publisherId?: string,
158230
): Promise<PublishResponse> {
159231
try {
160232
const response = await fetch(`${websiteUrl}/api/agents/publish`, {
@@ -165,6 +237,7 @@ async function publishAgentTemplate(
165237
},
166238
body: JSON.stringify({
167239
data,
240+
...(publisherId && { publisherId }),
168241
}),
169242
})
170243

@@ -205,6 +278,7 @@ async function publishAgentTemplate(
205278
error: errorMessage,
206279
details: result.details,
207280
statusCode: response.status,
281+
availablePublishers: result.availablePublishers,
208282
validationErrors: result.validationErrors,
209283
}
210284
}

npm-app/src/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,8 @@ For all commands and options, run 'codebuff' and then type 'help'.
137137
// Handle publish command
138138
if (args[0] === 'publish') {
139139
const agentName = args[1]
140-
await handlePublish(agentName)
140+
const publisherId = options.publisher
141+
await handlePublish(agentName, publisherId)
141142
process.exit(0)
142143
}
143144

web/src/app/api/agents/publish/route.ts

Lines changed: 89 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {
77
stringifyVersion,
88
versionExists,
99
} from '@codebuff/internal'
10-
import { desc, eq } from 'drizzle-orm'
10+
import { desc, eq, and, or } from 'drizzle-orm'
1111
import { NextResponse } from 'next/server'
1212
import { getServerSession } from 'next-auth'
1313
import { z } from 'zod'
@@ -22,6 +22,7 @@ import { logger } from '@/util/logger'
2222
// Schema for publishing an agent
2323
const publishAgentRequestSchema = z.object({
2424
data: DynamicAgentTemplateSchema,
25+
publisherId: z.string().optional(),
2526
})
2627

2728
export async function POST(request: NextRequest) {
@@ -53,7 +54,7 @@ export async function POST(request: NextRequest) {
5354
)
5455
}
5556

56-
const { data } = parseResult.data
57+
const { data, publisherId } = parseResult.data
5758
const agentId = data.id
5859

5960
const validationResult = validateAgents({
@@ -75,16 +76,35 @@ export async function POST(request: NextRequest) {
7576
)
7677
}
7778

78-
// Look up the user's latest publisher (by updated_at)
79-
const publisher = await db
80-
.select()
79+
// Look up publishers the user has access to (owned by user or their organizations)
80+
const publishers = await db
81+
.select({
82+
publisher: schema.publisher,
83+
organization: schema.org,
84+
})
8185
.from(schema.publisher)
82-
.where(eq(schema.publisher.user_id, userId))
86+
.leftJoin(schema.org, eq(schema.publisher.org_id, schema.org.id))
87+
.leftJoin(
88+
schema.orgMember,
89+
and(
90+
eq(schema.orgMember.org_id, schema.publisher.org_id),
91+
eq(schema.orgMember.user_id, userId)
92+
)
93+
)
94+
.where(
95+
or(
96+
eq(schema.publisher.user_id, userId),
97+
and(
98+
eq(schema.orgMember.user_id, userId),
99+
or(
100+
eq(schema.orgMember.role, 'owner'),
101+
eq(schema.orgMember.role, 'admin')
102+
)
103+
)
104+
)
105+
)
83106
.orderBy(desc(schema.publisher.updated_at))
84-
.limit(1)
85-
.then((rows) => rows[0])
86-
87-
if (!publisher) {
107+
if (publishers.length === 0) {
88108
return NextResponse.json(
89109
{
90110
error: 'No publisher associated with user',
@@ -94,6 +114,65 @@ export async function POST(request: NextRequest) {
94114
)
95115
}
96116

117+
// If a specific publisher is requested, find it
118+
let selectedPublisher
119+
if (publisherId) {
120+
const matchingPublisher = publishers.find(
121+
({ publisher }) => publisher.id === publisherId
122+
)
123+
if (!matchingPublisher) {
124+
// Format available publishers for error message
125+
const availablePublishers = publishers.map(
126+
({ publisher, organization }) => ({
127+
id: publisher.id,
128+
name: publisher.name,
129+
ownershipType: publisher.user_id ? 'user' : 'organization',
130+
organizationName: organization?.name,
131+
})
132+
)
133+
134+
return NextResponse.json(
135+
{
136+
error: 'Specified publisher not found or not accessible',
137+
details: `Publisher '${publisherId}' not found. You have access to: ${availablePublishers
138+
.map(
139+
(p) =>
140+
`${p.id} (${p.name}${p.organizationName ? ` - ${p.organizationName}` : ''})`
141+
)
142+
.join(', ')}`,
143+
availablePublishers,
144+
},
145+
{ status: 403 }
146+
)
147+
}
148+
selectedPublisher = matchingPublisher.publisher
149+
} else if (publishers.length > 1) {
150+
// Multiple publishers available, need to specify which one
151+
const availablePublishers = publishers.map(
152+
({ publisher, organization }) => ({
153+
id: publisher.id,
154+
name: publisher.name,
155+
ownershipType: publisher.user_id ? 'user' : 'organization',
156+
organizationName: organization?.name,
157+
})
158+
)
159+
160+
return NextResponse.json(
161+
{
162+
error: 'Multiple publishers available',
163+
details:
164+
'You have access to multiple publishers. Please specify which one to use with the --publisher flag.',
165+
availablePublishers,
166+
},
167+
{ status: 403 }
168+
)
169+
} else {
170+
// Use the only available publisher
171+
selectedPublisher = publishers[0].publisher
172+
}
173+
174+
const publisher = selectedPublisher
175+
97176
// Determine the version to use (auto-increment if not provided)
98177
let version: Version
99178
try {

0 commit comments

Comments
 (0)