mirror of
https://gitea.toothfairyai.com/ToothFairyAI/tf_code.git
synced 2026-03-31 14:22:27 +00:00
big format
This commit is contained in:
@@ -14,7 +14,11 @@ export const AuthCommand = cmd({
|
||||
command: "auth",
|
||||
describe: "manage credentials",
|
||||
builder: (yargs) =>
|
||||
yargs.command(AuthLoginCommand).command(AuthLogoutCommand).command(AuthListCommand).demandCommand(),
|
||||
yargs
|
||||
.command(AuthLoginCommand)
|
||||
.command(AuthLogoutCommand)
|
||||
.command(AuthListCommand)
|
||||
.demandCommand(),
|
||||
async handler() {},
|
||||
})
|
||||
|
||||
@@ -60,7 +64,9 @@ export const AuthListCommand = cmd({
|
||||
prompts.log.info(`${provider} ${UI.Style.TEXT_DIM}${envVar}`)
|
||||
}
|
||||
|
||||
prompts.outro(`${activeEnvVars.length} environment variable` + (activeEnvVars.length === 1 ? "" : "s"))
|
||||
prompts.outro(
|
||||
`${activeEnvVars.length} environment variable` + (activeEnvVars.length === 1 ? "" : "s"),
|
||||
)
|
||||
}
|
||||
},
|
||||
})
|
||||
@@ -80,7 +86,9 @@ export const AuthLoginCommand = cmd({
|
||||
UI.empty()
|
||||
prompts.intro("Add credential")
|
||||
if (args.url) {
|
||||
const wellknown = await fetch(`${args.url}/.well-known/opencode`).then((x) => x.json() as any)
|
||||
const wellknown = await fetch(`${args.url}/.well-known/opencode`).then(
|
||||
(x) => x.json() as any,
|
||||
)
|
||||
prompts.log.info(`Running \`${wellknown.auth.command.join(" ")}\``)
|
||||
const proc = Bun.spawn({
|
||||
cmd: wellknown.auth.command,
|
||||
@@ -102,223 +110,224 @@ export const AuthLoginCommand = cmd({
|
||||
prompts.outro("Done")
|
||||
return
|
||||
}
|
||||
await ModelsDev.refresh().catch(() => {})
|
||||
const providers = await ModelsDev.get()
|
||||
const priority: Record<string, number> = {
|
||||
opencode: 0,
|
||||
anthropic: 1,
|
||||
"github-copilot": 2,
|
||||
openai: 3,
|
||||
google: 4,
|
||||
openrouter: 5,
|
||||
vercel: 6,
|
||||
}
|
||||
let provider = await prompts.autocomplete({
|
||||
message: "Select provider",
|
||||
maxItems: 8,
|
||||
options: [
|
||||
...pipe(
|
||||
providers,
|
||||
values(),
|
||||
sortBy(
|
||||
(x) => priority[x.id] ?? 99,
|
||||
(x) => x.name ?? x.id,
|
||||
),
|
||||
map((x) => ({
|
||||
label: x.name,
|
||||
value: x.id,
|
||||
hint: priority[x.id] <= 1 ? "recommended" : undefined,
|
||||
})),
|
||||
),
|
||||
{
|
||||
value: "other",
|
||||
label: "Other",
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
if (prompts.isCancel(provider)) throw new UI.CancelledError()
|
||||
|
||||
const plugin = await Plugin.list().then((x) => x.find((x) => x.auth?.provider === provider))
|
||||
if (plugin && plugin.auth) {
|
||||
let index = 0
|
||||
if (plugin.auth.methods.length > 1) {
|
||||
const method = await prompts.select({
|
||||
message: "Login method",
|
||||
options: [
|
||||
...plugin.auth.methods.map((x, index) => ({
|
||||
label: x.label,
|
||||
value: index.toString(),
|
||||
await ModelsDev.refresh().catch(() => {})
|
||||
const providers = await ModelsDev.get()
|
||||
const priority: Record<string, number> = {
|
||||
opencode: 0,
|
||||
anthropic: 1,
|
||||
"github-copilot": 2,
|
||||
openai: 3,
|
||||
google: 4,
|
||||
openrouter: 5,
|
||||
vercel: 6,
|
||||
}
|
||||
let provider = await prompts.autocomplete({
|
||||
message: "Select provider",
|
||||
maxItems: 8,
|
||||
options: [
|
||||
...pipe(
|
||||
providers,
|
||||
values(),
|
||||
sortBy(
|
||||
(x) => priority[x.id] ?? 99,
|
||||
(x) => x.name ?? x.id,
|
||||
),
|
||||
map((x) => ({
|
||||
label: x.name,
|
||||
value: x.id,
|
||||
hint: priority[x.id] <= 1 ? "recommended" : undefined,
|
||||
})),
|
||||
],
|
||||
})
|
||||
if (prompts.isCancel(method)) throw new UI.CancelledError()
|
||||
index = parseInt(method)
|
||||
}
|
||||
const method = plugin.auth.methods[index]
|
||||
),
|
||||
{
|
||||
value: "other",
|
||||
label: "Other",
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
// Handle prompts for all auth types
|
||||
await new Promise((resolve) => setTimeout(resolve, 10))
|
||||
const inputs: Record<string, string> = {}
|
||||
if (method.prompts) {
|
||||
for (const prompt of method.prompts) {
|
||||
if (prompt.condition && !prompt.condition(inputs)) {
|
||||
continue
|
||||
}
|
||||
if (prompt.type === "select") {
|
||||
const value = await prompts.select({
|
||||
message: prompt.message,
|
||||
options: prompt.options,
|
||||
})
|
||||
if (prompts.isCancel(value)) throw new UI.CancelledError()
|
||||
inputs[prompt.key] = value
|
||||
} else {
|
||||
const value = await prompts.text({
|
||||
message: prompt.message,
|
||||
placeholder: prompt.placeholder,
|
||||
validate: prompt.validate ? (v) => prompt.validate!(v ?? "") : undefined,
|
||||
})
|
||||
if (prompts.isCancel(value)) throw new UI.CancelledError()
|
||||
inputs[prompt.key] = value
|
||||
}
|
||||
}
|
||||
}
|
||||
if (prompts.isCancel(provider)) throw new UI.CancelledError()
|
||||
|
||||
if (method.type === "oauth") {
|
||||
const authorize = await method.authorize(inputs)
|
||||
|
||||
if (authorize.url) {
|
||||
prompts.log.info("Go to: " + authorize.url)
|
||||
}
|
||||
|
||||
if (authorize.method === "auto") {
|
||||
if (authorize.instructions) {
|
||||
prompts.log.info(authorize.instructions)
|
||||
}
|
||||
const spinner = prompts.spinner()
|
||||
spinner.start("Waiting for authorization...")
|
||||
const result = await authorize.callback()
|
||||
if (result.type === "failed") {
|
||||
spinner.stop("Failed to authorize", 1)
|
||||
}
|
||||
if (result.type === "success") {
|
||||
const saveProvider = result.provider ?? provider
|
||||
if ("refresh" in result) {
|
||||
const { type: _, provider: __, refresh, access, expires, ...extraFields } = result
|
||||
await Auth.set(saveProvider, {
|
||||
type: "oauth",
|
||||
refresh,
|
||||
access,
|
||||
expires,
|
||||
...extraFields,
|
||||
})
|
||||
}
|
||||
if ("key" in result) {
|
||||
await Auth.set(saveProvider, {
|
||||
type: "api",
|
||||
key: result.key,
|
||||
})
|
||||
}
|
||||
spinner.stop("Login successful")
|
||||
}
|
||||
}
|
||||
|
||||
if (authorize.method === "code") {
|
||||
const code = await prompts.text({
|
||||
message: "Paste the authorization code here: ",
|
||||
validate: (x) => (x && x.length > 0 ? undefined : "Required"),
|
||||
const plugin = await Plugin.list().then((x) => x.find((x) => x.auth?.provider === provider))
|
||||
if (plugin && plugin.auth) {
|
||||
let index = 0
|
||||
if (plugin.auth.methods.length > 1) {
|
||||
const method = await prompts.select({
|
||||
message: "Login method",
|
||||
options: [
|
||||
...plugin.auth.methods.map((x, index) => ({
|
||||
label: x.label,
|
||||
value: index.toString(),
|
||||
})),
|
||||
],
|
||||
})
|
||||
if (prompts.isCancel(code)) throw new UI.CancelledError()
|
||||
const result = await authorize.callback(code)
|
||||
if (result.type === "failed") {
|
||||
prompts.log.error("Failed to authorize")
|
||||
}
|
||||
if (result.type === "success") {
|
||||
const saveProvider = result.provider ?? provider
|
||||
if ("refresh" in result) {
|
||||
const { type: _, provider: __, refresh, access, expires, ...extraFields } = result
|
||||
await Auth.set(saveProvider, {
|
||||
type: "oauth",
|
||||
refresh,
|
||||
access,
|
||||
expires,
|
||||
...extraFields,
|
||||
})
|
||||
if (prompts.isCancel(method)) throw new UI.CancelledError()
|
||||
index = parseInt(method)
|
||||
}
|
||||
const method = plugin.auth.methods[index]
|
||||
|
||||
// Handle prompts for all auth types
|
||||
await new Promise((resolve) => setTimeout(resolve, 10))
|
||||
const inputs: Record<string, string> = {}
|
||||
if (method.prompts) {
|
||||
for (const prompt of method.prompts) {
|
||||
if (prompt.condition && !prompt.condition(inputs)) {
|
||||
continue
|
||||
}
|
||||
if ("key" in result) {
|
||||
if (prompt.type === "select") {
|
||||
const value = await prompts.select({
|
||||
message: prompt.message,
|
||||
options: prompt.options,
|
||||
})
|
||||
if (prompts.isCancel(value)) throw new UI.CancelledError()
|
||||
inputs[prompt.key] = value
|
||||
} else {
|
||||
const value = await prompts.text({
|
||||
message: prompt.message,
|
||||
placeholder: prompt.placeholder,
|
||||
validate: prompt.validate ? (v) => prompt.validate!(v ?? "") : undefined,
|
||||
})
|
||||
if (prompts.isCancel(value)) throw new UI.CancelledError()
|
||||
inputs[prompt.key] = value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (method.type === "oauth") {
|
||||
const authorize = await method.authorize(inputs)
|
||||
|
||||
if (authorize.url) {
|
||||
prompts.log.info("Go to: " + authorize.url)
|
||||
}
|
||||
|
||||
if (authorize.method === "auto") {
|
||||
if (authorize.instructions) {
|
||||
prompts.log.info(authorize.instructions)
|
||||
}
|
||||
const spinner = prompts.spinner()
|
||||
spinner.start("Waiting for authorization...")
|
||||
const result = await authorize.callback()
|
||||
if (result.type === "failed") {
|
||||
spinner.stop("Failed to authorize", 1)
|
||||
}
|
||||
if (result.type === "success") {
|
||||
const saveProvider = result.provider ?? provider
|
||||
if ("refresh" in result) {
|
||||
const { type: _, provider: __, refresh, access, expires, ...extraFields } = result
|
||||
await Auth.set(saveProvider, {
|
||||
type: "oauth",
|
||||
refresh,
|
||||
access,
|
||||
expires,
|
||||
...extraFields,
|
||||
})
|
||||
}
|
||||
if ("key" in result) {
|
||||
await Auth.set(saveProvider, {
|
||||
type: "api",
|
||||
key: result.key,
|
||||
})
|
||||
}
|
||||
spinner.stop("Login successful")
|
||||
}
|
||||
}
|
||||
|
||||
if (authorize.method === "code") {
|
||||
const code = await prompts.text({
|
||||
message: "Paste the authorization code here: ",
|
||||
validate: (x) => (x && x.length > 0 ? undefined : "Required"),
|
||||
})
|
||||
if (prompts.isCancel(code)) throw new UI.CancelledError()
|
||||
const result = await authorize.callback(code)
|
||||
if (result.type === "failed") {
|
||||
prompts.log.error("Failed to authorize")
|
||||
}
|
||||
if (result.type === "success") {
|
||||
const saveProvider = result.provider ?? provider
|
||||
if ("refresh" in result) {
|
||||
const { type: _, provider: __, refresh, access, expires, ...extraFields } = result
|
||||
await Auth.set(saveProvider, {
|
||||
type: "oauth",
|
||||
refresh,
|
||||
access,
|
||||
expires,
|
||||
...extraFields,
|
||||
})
|
||||
}
|
||||
if ("key" in result) {
|
||||
await Auth.set(saveProvider, {
|
||||
type: "api",
|
||||
key: result.key,
|
||||
})
|
||||
}
|
||||
prompts.log.success("Login successful")
|
||||
}
|
||||
}
|
||||
|
||||
prompts.outro("Done")
|
||||
return
|
||||
}
|
||||
|
||||
if (method.type === "api") {
|
||||
if (method.authorize) {
|
||||
const result = await method.authorize(inputs)
|
||||
if (result.type === "failed") {
|
||||
prompts.log.error("Failed to authorize")
|
||||
}
|
||||
if (result.type === "success") {
|
||||
const saveProvider = result.provider ?? provider
|
||||
await Auth.set(saveProvider, {
|
||||
type: "api",
|
||||
key: result.key,
|
||||
})
|
||||
prompts.log.success("Login successful")
|
||||
}
|
||||
prompts.log.success("Login successful")
|
||||
prompts.outro("Done")
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (provider === "other") {
|
||||
provider = await prompts.text({
|
||||
message: "Enter provider id",
|
||||
validate: (x) =>
|
||||
x && x.match(/^[0-9a-z-]+$/) ? undefined : "a-z, 0-9 and hyphens only",
|
||||
})
|
||||
if (prompts.isCancel(provider)) throw new UI.CancelledError()
|
||||
provider = provider.replace(/^@ai-sdk\//, "")
|
||||
if (prompts.isCancel(provider)) throw new UI.CancelledError()
|
||||
prompts.log.warn(
|
||||
`This only stores a credential for ${provider} - you will need configure it in opencode.json, check the docs for examples.`,
|
||||
)
|
||||
}
|
||||
|
||||
if (provider === "amazon-bedrock") {
|
||||
prompts.log.info(
|
||||
"Amazon bedrock can be configured with standard AWS environment variables like AWS_BEARER_TOKEN_BEDROCK, AWS_PROFILE or AWS_ACCESS_KEY_ID",
|
||||
)
|
||||
prompts.outro("Done")
|
||||
return
|
||||
}
|
||||
|
||||
if (method.type === "api") {
|
||||
if (method.authorize) {
|
||||
const result = await method.authorize(inputs)
|
||||
if (result.type === "failed") {
|
||||
prompts.log.error("Failed to authorize")
|
||||
}
|
||||
if (result.type === "success") {
|
||||
const saveProvider = result.provider ?? provider
|
||||
await Auth.set(saveProvider, {
|
||||
type: "api",
|
||||
key: result.key,
|
||||
})
|
||||
prompts.log.success("Login successful")
|
||||
}
|
||||
prompts.outro("Done")
|
||||
return
|
||||
}
|
||||
if (provider === "opencode") {
|
||||
prompts.log.info("Create an api key at https://opencode.ai/auth")
|
||||
}
|
||||
}
|
||||
|
||||
if (provider === "other") {
|
||||
provider = await prompts.text({
|
||||
message: "Enter provider id",
|
||||
validate: (x) => (x && x.match(/^[0-9a-z-]+$/) ? undefined : "a-z, 0-9 and hyphens only"),
|
||||
if (provider === "vercel") {
|
||||
prompts.log.info("You can create an api key at https://vercel.link/ai-gateway-token")
|
||||
}
|
||||
|
||||
const key = await prompts.password({
|
||||
message: "Enter your API key",
|
||||
validate: (x) => (x && x.length > 0 ? undefined : "Required"),
|
||||
})
|
||||
if (prompts.isCancel(key)) throw new UI.CancelledError()
|
||||
await Auth.set(provider, {
|
||||
type: "api",
|
||||
key,
|
||||
})
|
||||
if (prompts.isCancel(provider)) throw new UI.CancelledError()
|
||||
provider = provider.replace(/^@ai-sdk\//, "")
|
||||
if (prompts.isCancel(provider)) throw new UI.CancelledError()
|
||||
prompts.log.warn(
|
||||
`This only stores a credential for ${provider} - you will need configure it in opencode.json, check the docs for examples.`,
|
||||
)
|
||||
}
|
||||
|
||||
if (provider === "amazon-bedrock") {
|
||||
prompts.log.info(
|
||||
"Amazon bedrock can be configured with standard AWS environment variables like AWS_BEARER_TOKEN_BEDROCK, AWS_PROFILE or AWS_ACCESS_KEY_ID",
|
||||
)
|
||||
prompts.outro("Done")
|
||||
return
|
||||
}
|
||||
|
||||
if (provider === "opencode") {
|
||||
prompts.log.info("Create an api key at https://opencode.ai/auth")
|
||||
}
|
||||
|
||||
if (provider === "vercel") {
|
||||
prompts.log.info("You can create an api key at https://vercel.link/ai-gateway-token")
|
||||
}
|
||||
|
||||
const key = await prompts.password({
|
||||
message: "Enter your API key",
|
||||
validate: (x) => (x && x.length > 0 ? undefined : "Required"),
|
||||
})
|
||||
if (prompts.isCancel(key)) throw new UI.CancelledError()
|
||||
await Auth.set(provider, {
|
||||
type: "api",
|
||||
key,
|
||||
})
|
||||
|
||||
prompts.outro("Done")
|
||||
},
|
||||
})
|
||||
},
|
||||
|
||||
@@ -7,7 +7,11 @@ import { EOL } from "os"
|
||||
export const LSPCommand = cmd({
|
||||
command: "lsp",
|
||||
builder: (yargs) =>
|
||||
yargs.command(DiagnosticsCommand).command(SymbolsCommand).command(DocumentSymbolsCommand).demandCommand(),
|
||||
yargs
|
||||
.command(DiagnosticsCommand)
|
||||
.command(SymbolsCommand)
|
||||
.command(DocumentSymbolsCommand)
|
||||
.demandCommand(),
|
||||
async handler() {},
|
||||
})
|
||||
|
||||
|
||||
@@ -6,7 +6,8 @@ import { cmd } from "../cmd"
|
||||
|
||||
export const RipgrepCommand = cmd({
|
||||
command: "rg",
|
||||
builder: (yargs) => yargs.command(TreeCommand).command(FilesCommand).command(SearchCommand).demandCommand(),
|
||||
builder: (yargs) =>
|
||||
yargs.command(TreeCommand).command(FilesCommand).command(SearchCommand).demandCommand(),
|
||||
async handler() {},
|
||||
})
|
||||
|
||||
@@ -18,7 +19,9 @@ const TreeCommand = cmd({
|
||||
}),
|
||||
async handler(args) {
|
||||
await bootstrap(process.cwd(), async () => {
|
||||
process.stdout.write(await Ripgrep.tree({ cwd: Instance.directory, limit: args.limit }) + EOL)
|
||||
process.stdout.write(
|
||||
(await Ripgrep.tree({ cwd: Instance.directory, limit: args.limit })) + EOL,
|
||||
)
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
@@ -4,7 +4,8 @@ import { cmd } from "../cmd"
|
||||
|
||||
export const SnapshotCommand = cmd({
|
||||
command: "snapshot",
|
||||
builder: (yargs) => yargs.command(TrackCommand).command(PatchCommand).command(DiffCommand).demandCommand(),
|
||||
builder: (yargs) =>
|
||||
yargs.command(TrackCommand).command(PatchCommand).command(DiffCommand).demandCommand(),
|
||||
async handler() {},
|
||||
})
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ export const GenerateCommand = {
|
||||
handler: async () => {
|
||||
const specs = await Server.openapi()
|
||||
const json = JSON.stringify(specs, null, 2)
|
||||
|
||||
|
||||
// Wait for stdout to finish writing before process.exit() is called
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
process.stdout.write(json, (err) => {
|
||||
|
||||
@@ -189,7 +189,9 @@ export const GithubInstallCommand = cmd({
|
||||
async function getAppInfo() {
|
||||
const project = Instance.project
|
||||
if (project.vcs !== "git") {
|
||||
prompts.log.error(`Could not find git repository. Please run this command from a git repository.`)
|
||||
prompts.log.error(
|
||||
`Could not find git repository. Please run this command from a git repository.`,
|
||||
)
|
||||
throw new UI.CancelledError()
|
||||
}
|
||||
|
||||
@@ -202,9 +204,13 @@ export const GithubInstallCommand = cmd({
|
||||
// ie. git@github.com:sst/opencode
|
||||
// ie. ssh://git@github.com/sst/opencode.git
|
||||
// ie. ssh://git@github.com/sst/opencode
|
||||
const parsed = info.match(/^(?:(?:https?|ssh):\/\/)?(?:git@)?github\.com[:/]([^/]+)\/([^/.]+?)(?:\.git)?$/)
|
||||
const parsed = info.match(
|
||||
/^(?:(?:https?|ssh):\/\/)?(?:git@)?github\.com[:/]([^/]+)\/([^/.]+?)(?:\.git)?$/,
|
||||
)
|
||||
if (!parsed) {
|
||||
prompts.log.error(`Could not find git repository. Please run this command from a git repository.`)
|
||||
prompts.log.error(
|
||||
`Could not find git repository. Please run this command from a git repository.`,
|
||||
)
|
||||
throw new UI.CancelledError()
|
||||
}
|
||||
const [, owner, repo] = parsed
|
||||
@@ -445,7 +451,9 @@ export const GithubRunCommand = cmd({
|
||||
const summary = await summarize(response)
|
||||
await pushToLocalBranch(summary)
|
||||
}
|
||||
const hasShared = prData.comments.nodes.some((c) => c.body.includes(`${shareBaseUrl}/s/${shareId}`))
|
||||
const hasShared = prData.comments.nodes.some((c) =>
|
||||
c.body.includes(`${shareBaseUrl}/s/${shareId}`),
|
||||
)
|
||||
await updateComment(`${response}${footer({ image: !hasShared })}`)
|
||||
}
|
||||
// Fork PR
|
||||
@@ -457,7 +465,9 @@ export const GithubRunCommand = cmd({
|
||||
const summary = await summarize(response)
|
||||
await pushToForkBranch(summary, prData)
|
||||
}
|
||||
const hasShared = prData.comments.nodes.some((c) => c.body.includes(`${shareBaseUrl}/s/${shareId}`))
|
||||
const hasShared = prData.comments.nodes.some((c) =>
|
||||
c.body.includes(`${shareBaseUrl}/s/${shareId}`),
|
||||
)
|
||||
await updateComment(`${response}${footer({ image: !hasShared })}`)
|
||||
}
|
||||
}
|
||||
@@ -547,8 +557,12 @@ export const GithubRunCommand = cmd({
|
||||
// ie. <img alt="Image" src="https://github.com/user-attachments/assets/xxxx" />
|
||||
// ie. [api.json](https://github.com/user-attachments/files/21433810/api.json)
|
||||
// ie. 
|
||||
const mdMatches = prompt.matchAll(/!?\[.*?\]\((https:\/\/github\.com\/user-attachments\/[^)]+)\)/gi)
|
||||
const tagMatches = prompt.matchAll(/<img .*?src="(https:\/\/github\.com\/user-attachments\/[^"]+)" \/>/gi)
|
||||
const mdMatches = prompt.matchAll(
|
||||
/!?\[.*?\]\((https:\/\/github\.com\/user-attachments\/[^)]+)\)/gi,
|
||||
)
|
||||
const tagMatches = prompt.matchAll(
|
||||
/<img .*?src="(https:\/\/github\.com\/user-attachments\/[^"]+)" \/>/gi,
|
||||
)
|
||||
const matches = [...mdMatches, ...tagMatches].sort((a, b) => a.index - b.index)
|
||||
console.log("Images", JSON.stringify(matches, null, 2))
|
||||
|
||||
@@ -573,7 +587,10 @@ export const GithubRunCommand = cmd({
|
||||
|
||||
// Replace img tag with file path, ie. @image.png
|
||||
const replacement = `@${filename}`
|
||||
prompt = prompt.slice(0, start + offset) + replacement + prompt.slice(start + offset + tag.length)
|
||||
prompt =
|
||||
prompt.slice(0, start + offset) +
|
||||
replacement +
|
||||
prompt.slice(start + offset + tag.length)
|
||||
offset += replacement.length - tag.length
|
||||
|
||||
const contentType = res.headers.get("content-type")
|
||||
@@ -856,7 +873,8 @@ Co-authored-by: ${actor} <${actor}@users.noreply.github.com>"`
|
||||
throw new Error(`Failed to check permissions for user ${actor}: ${error}`)
|
||||
}
|
||||
|
||||
if (!["admin", "write"].includes(permission)) throw new Error(`User ${actor} does not have write permissions`)
|
||||
if (!["admin", "write"].includes(permission))
|
||||
throw new Error(`User ${actor} does not have write permissions`)
|
||||
}
|
||||
|
||||
async function createComment() {
|
||||
@@ -904,7 +922,9 @@ Co-authored-by: ${actor} <${actor}@users.noreply.github.com>"`
|
||||
|
||||
return `<a href="${shareBaseUrl}/s/${shareId}"><img width="200" alt="${titleAlt}" src="https://social-cards.sst.dev/opencode-share/${title64}.png?model=${providerID}/${modelID}&version=${session.version}&id=${shareId}" /></a>\n`
|
||||
})()
|
||||
const shareUrl = shareId ? `[opencode session](${shareBaseUrl}/s/${shareId}) | ` : ""
|
||||
const shareUrl = shareId
|
||||
? `[opencode session](${shareBaseUrl}/s/${shareId}) | `
|
||||
: ""
|
||||
return `\n\n${image}${shareUrl}[github run](${runUrl})`
|
||||
}
|
||||
|
||||
@@ -1080,9 +1100,13 @@ query($owner: String!, $repo: String!, $number: Int!) {
|
||||
})
|
||||
.map((c) => `- ${c.author.login} at ${c.createdAt}: ${c.body}`)
|
||||
|
||||
const files = (pr.files.nodes || []).map((f) => `- ${f.path} (${f.changeType}) +${f.additions}/-${f.deletions}`)
|
||||
const files = (pr.files.nodes || []).map(
|
||||
(f) => `- ${f.path} (${f.changeType}) +${f.additions}/-${f.deletions}`,
|
||||
)
|
||||
const reviewData = (pr.reviews.nodes || []).map((r) => {
|
||||
const comments = (r.comments.nodes || []).map((c) => ` - ${c.path}:${c.line ?? "?"}: ${c.body}`)
|
||||
const comments = (r.comments.nodes || []).map(
|
||||
(c) => ` - ${c.path}:${c.line ?? "?"}: ${c.body}`,
|
||||
)
|
||||
return [
|
||||
`- ${r.author.login} at ${r.submittedAt}:`,
|
||||
` - Review body: ${r.body}`,
|
||||
@@ -1104,9 +1128,15 @@ query($owner: String!, $repo: String!, $number: Int!) {
|
||||
`Deletions: ${pr.deletions}`,
|
||||
`Total Commits: ${pr.commits.totalCount}`,
|
||||
`Changed Files: ${pr.files.nodes.length} files`,
|
||||
...(comments.length > 0 ? ["<pull_request_comments>", ...comments, "</pull_request_comments>"] : []),
|
||||
...(files.length > 0 ? ["<pull_request_changed_files>", ...files, "</pull_request_changed_files>"] : []),
|
||||
...(reviewData.length > 0 ? ["<pull_request_reviews>", ...reviewData, "</pull_request_reviews>"] : []),
|
||||
...(comments.length > 0
|
||||
? ["<pull_request_comments>", ...comments, "</pull_request_comments>"]
|
||||
: []),
|
||||
...(files.length > 0
|
||||
? ["<pull_request_changed_files>", ...files, "</pull_request_changed_files>"]
|
||||
: []),
|
||||
...(reviewData.length > 0
|
||||
? ["<pull_request_reviews>", ...reviewData, "</pull_request_reviews>"]
|
||||
: []),
|
||||
"</pull_request>",
|
||||
].join("\n")
|
||||
}
|
||||
|
||||
@@ -137,7 +137,9 @@ export const RunCommand = cmd({
|
||||
|
||||
const outputJsonEvent = (type: string, data: any) => {
|
||||
if (args.format === "json") {
|
||||
process.stdout.write(JSON.stringify({ type, timestamp: Date.now(), sessionID, ...data }) + EOL)
|
||||
process.stdout.write(
|
||||
JSON.stringify({ type, timestamp: Date.now(), sessionID, ...data }) + EOL,
|
||||
)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
@@ -157,7 +159,9 @@ export const RunCommand = cmd({
|
||||
const [tool, color] = TOOL[part.tool] ?? [part.tool, UI.Style.TEXT_INFO_BOLD]
|
||||
const title =
|
||||
part.state.title ||
|
||||
(Object.keys(part.state.input).length > 0 ? JSON.stringify(part.state.input) : "Unknown")
|
||||
(Object.keys(part.state.input).length > 0
|
||||
? JSON.stringify(part.state.input)
|
||||
: "Unknown")
|
||||
printEvent(color, tool, title)
|
||||
if (part.tool === "bash" && part.state.output?.trim()) {
|
||||
UI.println()
|
||||
@@ -280,7 +284,10 @@ export const RunCommand = cmd({
|
||||
}
|
||||
|
||||
const cfgResult = await sdk.config.get()
|
||||
if (cfgResult.data && (cfgResult.data.share === "auto" || Flag.OPENCODE_AUTO_SHARE || args.share)) {
|
||||
if (
|
||||
cfgResult.data &&
|
||||
(cfgResult.data.share === "auto" || Flag.OPENCODE_AUTO_SHARE || args.share)
|
||||
) {
|
||||
const shareResult = await sdk.session.share({ path: { id: sessionID } }).catch((error) => {
|
||||
if (error instanceof Error && error.message.includes("disabled")) {
|
||||
UI.println(UI.Style.TEXT_DANGER_BOLD + "! " + error.message)
|
||||
@@ -333,7 +340,10 @@ export const RunCommand = cmd({
|
||||
}
|
||||
|
||||
const cfgResult = await sdk.config.get()
|
||||
if (cfgResult.data && (cfgResult.data.share === "auto" || Flag.OPENCODE_AUTO_SHARE || args.share)) {
|
||||
if (
|
||||
cfgResult.data &&
|
||||
(cfgResult.data.share === "auto" || Flag.OPENCODE_AUTO_SHARE || args.share)
|
||||
) {
|
||||
const shareResult = await sdk.session.share({ path: { id: sessionID } }).catch((error) => {
|
||||
if (error instanceof Error && error.message.includes("disabled")) {
|
||||
UI.println(UI.Style.TEXT_DANGER_BOLD + "! " + error.message)
|
||||
|
||||
@@ -52,7 +52,11 @@ export function DialogModel() {
|
||||
description: provider.name,
|
||||
category: provider.name,
|
||||
})),
|
||||
filter((x) => Boolean(ref()?.filter) || !local.model.recent().find((y) => isDeepEqual(y, x.value))),
|
||||
filter(
|
||||
(x) =>
|
||||
Boolean(ref()?.filter) ||
|
||||
!local.model.recent().find((y) => isDeepEqual(y, x.value)),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -20,8 +20,8 @@ export function DialogSessionList() {
|
||||
|
||||
const deleteKeybind = "ctrl+d"
|
||||
|
||||
const currentSessionID = createMemo(() =>
|
||||
route.data.type === "session" ? route.data.sessionID : undefined
|
||||
const currentSessionID = createMemo(() =>
|
||||
route.data.type === "session" ? route.data.sessionID : undefined,
|
||||
)
|
||||
|
||||
const options = createMemo(() => {
|
||||
|
||||
@@ -3,9 +3,19 @@ import { TextAttributes } from "@opentui/core"
|
||||
import { For } from "solid-js"
|
||||
import { useTheme } from "@tui/context/theme"
|
||||
|
||||
const LOGO_LEFT = [` `, `█▀▀█ █▀▀█ █▀▀█ █▀▀▄`, `█░░█ █░░█ █▀▀▀ █░░█`, `▀▀▀▀ █▀▀▀ ▀▀▀▀ ▀ ▀`]
|
||||
const LOGO_LEFT = [
|
||||
` `,
|
||||
`█▀▀█ █▀▀█ █▀▀█ █▀▀▄`,
|
||||
`█░░█ █░░█ █▀▀▀ █░░█`,
|
||||
`▀▀▀▀ █▀▀▀ ▀▀▀▀ ▀ ▀`,
|
||||
]
|
||||
|
||||
const LOGO_RIGHT = [` ▄ `, `█▀▀▀ █▀▀█ █▀▀█ █▀▀█`, `█░░░ █░░█ █░░█ █▀▀▀`, `▀▀▀▀ ▀▀▀▀ ▀▀▀▀ ▀▀▀▀`]
|
||||
const LOGO_RIGHT = [
|
||||
` ▄ `,
|
||||
`█▀▀▀ █▀▀█ █▀▀█ █▀▀█`,
|
||||
`█░░░ █░░█ █░░█ █▀▀▀`,
|
||||
`▀▀▀▀ ▀▀▀▀ ▀▀▀▀ ▀▀▀▀`,
|
||||
]
|
||||
|
||||
export function Logo() {
|
||||
const { theme } = useTheme()
|
||||
|
||||
@@ -17,13 +17,11 @@ export const { use: useRoute, provider: RouteProvider } = createSimpleContext({
|
||||
init: (props: { data?: Route }) => {
|
||||
const [store, setStore] = createStore<Route>(
|
||||
props.data ??
|
||||
(
|
||||
process.env["OPENCODE_ROUTE"]
|
||||
(process.env["OPENCODE_ROUTE"]
|
||||
? JSON.parse(process.env["OPENCODE_ROUTE"])
|
||||
: {
|
||||
type: "home",
|
||||
}
|
||||
),
|
||||
type: "home",
|
||||
}),
|
||||
)
|
||||
|
||||
return {
|
||||
|
||||
@@ -269,6 +269,7 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
|
||||
},
|
||||
async sync(sessionID: string) {
|
||||
const now = Date.now()
|
||||
console.log("syncing", sessionID)
|
||||
const [session, messages, todo, diff] = await Promise.all([
|
||||
sdk.client.session.get({ path: { id: sessionID }, throwOnError: true }),
|
||||
sdk.client.session.messages({ path: { id: sessionID } }),
|
||||
|
||||
@@ -218,4 +218,4 @@
|
||||
"light": "nightOwlFg"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,9 @@ import { useRoute } from "@tui/context/route"
|
||||
export function DialogMessage(props: { messageID: string; sessionID: string }) {
|
||||
const sync = useSync()
|
||||
const sdk = useSDK()
|
||||
const message = createMemo(() => sync.data.message[props.sessionID]?.find((x) => x.id === props.messageID))
|
||||
const message = createMemo(() =>
|
||||
sync.data.message[props.sessionID]?.find((x) => x.id === props.messageID),
|
||||
)
|
||||
const route = useRoute()
|
||||
|
||||
return (
|
||||
|
||||
@@ -19,7 +19,9 @@ export function DialogTimeline(props: { sessionID: string; onMove: (messageID: s
|
||||
const result = [] as DialogSelectOption<string>[]
|
||||
for (const message of messages) {
|
||||
if (message.role !== "user") continue
|
||||
const part = (sync.data.part[message.id] ?? []).find((x) => x.type === "text" && !x.synthetic) as TextPart
|
||||
const part = (sync.data.part[message.id] ?? []).find(
|
||||
(x) => x.type === "text" && !x.synthetic,
|
||||
) as TextPart
|
||||
if (!part) continue
|
||||
result.push({
|
||||
title: part.text.replace(/\n/g, " "),
|
||||
@@ -33,5 +35,11 @@ export function DialogTimeline(props: { sessionID: string; onMove: (messageID: s
|
||||
return result
|
||||
})
|
||||
|
||||
return <DialogSelect onMove={(option) => props.onMove(option.value)} title="Timeline" options={options()} />
|
||||
return (
|
||||
<DialogSelect
|
||||
onMove={(option) => props.onMove(option.value)}
|
||||
title="Timeline"
|
||||
options={options()}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -105,14 +105,15 @@ export function Session() {
|
||||
const sidebarVisible = createMemo(() => sidebar() === "show" || (sidebar() === "auto" && wide()))
|
||||
const contentWidth = createMemo(() => dimensions().width - (sidebarVisible() ? 42 : 0) - 4)
|
||||
|
||||
createEffect(() => {
|
||||
sync.session.sync(route.sessionID).catch(() => {
|
||||
createEffect(async () => {
|
||||
await sync.session.sync(route.sessionID).catch(() => {
|
||||
toast.show({
|
||||
message: `Session not found: ${route.sessionID}`,
|
||||
variant: "error",
|
||||
})
|
||||
return navigate({ type: "home" })
|
||||
})
|
||||
scroll.scrollBy(100_000)
|
||||
})
|
||||
|
||||
const toast = useToast()
|
||||
|
||||
@@ -41,7 +41,12 @@ export const TuiSpawnCommand = cmd({
|
||||
)
|
||||
cwd = new URL("../../../../", import.meta.url).pathname
|
||||
} else cmd.push(process.execPath)
|
||||
cmd.push("attach", server.url.toString(), "--dir", args.project ? path.resolve(args.project) : process.cwd())
|
||||
cmd.push(
|
||||
"attach",
|
||||
server.url.toString(),
|
||||
"--dir",
|
||||
args.project ? path.resolve(args.project) : process.cwd(),
|
||||
)
|
||||
const proc = Bun.spawn({
|
||||
cmd,
|
||||
cwd,
|
||||
|
||||
@@ -53,7 +53,9 @@ export function DialogConfirm(props: DialogConfirmProps) {
|
||||
dialog.clear()
|
||||
}}
|
||||
>
|
||||
<text fg={key === store.active ? theme.background : theme.textMuted}>{Locale.titlecase(key)}</text>
|
||||
<text fg={key === store.active ? theme.background : theme.textMuted}>
|
||||
{Locale.titlecase(key)}
|
||||
</text>
|
||||
</box>
|
||||
)}
|
||||
</For>
|
||||
|
||||
@@ -5,7 +5,10 @@ import { join } from "node:path"
|
||||
import { CliRenderer } from "@opentui/core"
|
||||
|
||||
export namespace Editor {
|
||||
export async function open(opts: { value: string; renderer: CliRenderer }): Promise<string | undefined> {
|
||||
export async function open(opts: {
|
||||
value: string
|
||||
renderer: CliRenderer
|
||||
}): Promise<string | undefined> {
|
||||
const editor = process.env["EDITOR"]
|
||||
if (!editor) return
|
||||
|
||||
|
||||
@@ -27,7 +27,9 @@ export const UpgradeCommand = {
|
||||
const detectedMethod = await Installation.method()
|
||||
const method = (args.method as Installation.Method) ?? detectedMethod
|
||||
if (method === "unknown") {
|
||||
prompts.log.error(`opencode is installed to ${process.execPath} and may be managed by a package manager`)
|
||||
prompts.log.error(
|
||||
`opencode is installed to ${process.execPath} and may be managed by a package manager`,
|
||||
)
|
||||
const install = await prompts.select({
|
||||
message: "Install anyways?",
|
||||
options: [
|
||||
|
||||
Reference in New Issue
Block a user