feat: roolbac

This commit is contained in:
Gab
2026-03-24 15:06:34 +11:00
parent 7c015708cb
commit ff2d13015d
23 changed files with 1847 additions and 203 deletions

View File

@@ -85,7 +85,7 @@ export const McpListCommand = cmd({
if (servers.length === 0) {
prompts.log.warn("No MCP servers configured")
prompts.outro("Add servers with: opencode mcp add")
prompts.outro("Add servers with: tfcode mcp add")
return
}

View File

@@ -6,7 +6,7 @@ import { git } from "@/util/git"
export const PrCommand = cmd({
command: "pr <number>",
describe: "fetch and checkout a GitHub PR branch, then run opencode",
describe: "fetch and checkout a GitHub PR branch, then run tfcode",
builder: (yargs) =>
yargs.positional("number", {
type: "number",
@@ -82,15 +82,15 @@ export const PrCommand = cmd({
})
}
// Check for opencode session link in PR body
// Check for tfcode session link in PR body
if (prInfo && prInfo.body) {
const sessionMatch = prInfo.body.match(/https:\/\/opncd\.ai\/s\/([a-zA-Z0-9_-]+)/)
if (sessionMatch) {
const sessionUrl = sessionMatch[0]
UI.println(`Found opencode session: ${sessionUrl}`)
UI.println(`Found tfcode session: ${sessionUrl}`)
UI.println(`Importing session...`)
const importResult = await Process.text(["opencode", "import", sessionUrl], {
const importResult = await Process.text(["tfcode", "import", sessionUrl], {
nothrow: true,
})
if (importResult.code === 0) {
@@ -109,18 +109,18 @@ export const PrCommand = cmd({
UI.println(`Successfully checked out PR #${prNumber} as branch '${localBranchName}'`)
UI.println()
UI.println("Starting opencode...")
UI.println("Starting tfcode...")
UI.println()
const opencodeArgs = sessionId ? ["-s", sessionId] : []
const opencodeProcess = Process.spawn(["opencode", ...opencodeArgs], {
const tfcodeArgs = sessionId ? ["-s", sessionId] : []
const tfcodeProcess = Process.spawn(["tfcode", ...tfcodeArgs], {
stdin: "inherit",
stdout: "inherit",
stderr: "inherit",
cwd: process.cwd(),
})
const code = await opencodeProcess.exited
if (code !== 0) throw new Error(`opencode exited with code ${code}`)
const code = await tfcodeProcess.exited
if (code !== 0) throw new Error(`tfcode exited with code ${code}`)
},
})
},

View File

@@ -220,7 +220,7 @@ function normalizePath(input?: string) {
export const RunCommand = cmd({
command: "run [message..]",
describe: "run opencode with a message",
describe: "run tfcode with a message",
builder: (yargs: Argv) => {
return yargs
.positional("message", {
@@ -278,7 +278,7 @@ export const RunCommand = cmd({
})
.option("attach", {
type: "string",
describe: "attach to a running opencode server (e.g., http://localhost:4096)",
describe: "attach to a running tfcode server (e.g., http://localhost:4096)",
})
.option("password", {
alias: ["p"],

View File

@@ -9,14 +9,14 @@ import { Installation } from "../../installation"
export const ServeCommand = cmd({
command: "serve",
builder: (yargs) => withNetworkOptions(yargs),
describe: "starts a headless opencode server",
describe: "starts a headless tfcode server",
handler: async (args) => {
if (!Flag.OPENCODE_SERVER_PASSWORD) {
console.log("Warning: OPENCODE_SERVER_PASSWORD is not set; server is unsecured.")
}
const opts = await resolveNetworkOptions(args)
const server = Server.listen(opts)
console.log(`opencode server listening on http://${server.hostname}:${server.port}`)
console.log(`tfcode server listening on http://${server.hostname}:${server.port}`)
await new Promise(() => {})
await server.stop()

View File

@@ -8,7 +8,7 @@ import { existsSync } from "fs"
export const AttachCommand = cmd({
command: "attach <url>",
describe: "attach to a running opencode server",
describe: "attach to a running tfcode server",
builder: (yargs) =>
yargs
.positional("url", {

View File

@@ -64,12 +64,12 @@ async function input(value?: string) {
export const TuiThreadCommand = cmd({
command: "$0 [project]",
describe: "start opencode tui",
describe: "start tfcode tui",
builder: (yargs) =>
withNetworkOptions(yargs)
.positional("project", {
type: "string",
describe: "path to start opencode in",
describe: "path to start tfcode in",
})
.option("model", {
type: "string",

View File

@@ -24,7 +24,7 @@ interface RemovalTargets {
export const UninstallCommand = {
command: "uninstall",
describe: "uninstall opencode and remove all related files",
describe: "uninstall tfcode and remove all related files",
builder: (yargs: Argv) =>
yargs
.option("keep-config", {

View File

@@ -5,7 +5,7 @@ import { Installation } from "../../installation"
export const UpgradeCommand = {
command: "upgrade [target]",
describe: "upgrade opencode to the latest or a specific version",
describe: "upgrade tfcode to the latest or a specific version",
builder: (yargs: Argv) => {
return yargs
.positional("target", {
@@ -27,7 +27,7 @@ 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(`tfcode is installed to ${process.execPath} and may be managed by a package manager`)
const install = await prompts.select({
message: "Install anyways?",
options: [
@@ -45,7 +45,7 @@ export const UpgradeCommand = {
const target = args.target ? args.target.replace(/^v/, "") : await Installation.latest()
if (Installation.VERSION === target) {
prompts.log.warn(`opencode upgrade skipped: ${target} is already installed`)
prompts.log.warn(`tfcode upgrade skipped: ${target} is already installed`)
prompts.outro("Done")
return
}

View File

@@ -31,7 +31,7 @@ function getNetworkIPs() {
export const WebCommand = cmd({
command: "web",
builder: (yargs) => withNetworkOptions(yargs),
describe: "start opencode server and open web interface",
describe: "start tfcode server and open web interface",
handler: async (args) => {
if (!Flag.OPENCODE_SERVER_PASSWORD) {
UI.println(UI.Style.TEXT_WARNING_BOLD + "! " + "OPENCODE_SERVER_PASSWORD is not set; server is unsecured.")

View File

@@ -6,14 +6,14 @@ import { UI } from "./ui"
export function FormatError(input: unknown) {
if (MCP.Failed.isInstance(input))
return `MCP server "${input.data.name}" failed. Note, opencode does not support MCP authentication yet.`
return `MCP server "${input.data.name}" failed. Note, tfcode does not support MCP authentication yet.`
if (Provider.ModelNotFoundError.isInstance(input)) {
const { providerID, modelID, suggestions } = input.data
return [
`Model not found: ${providerID}/${modelID}`,
...(Array.isArray(suggestions) && suggestions.length ? ["Did you mean: " + suggestions.join(", ")] : []),
`Try: \`opencode models\` to list available models`,
`Or check your config (opencode.json) provider/model names`,
`Try: \`tfcode models\` to list available models`,
`Or check your config (tfcode.json) provider/model names`,
].join("\n")
}
if (Provider.InitError.isInstance(input)) {

View File

@@ -3,4 +3,4 @@ export const logo = {
right: [" ▄ ", "█▀▀▀ █▀▀█ █▀▀█ █▀▀█", "█___ █__█ █__█ █^^^", "▀▀▀▀ ▀▀▀▀ ▀▀▀▀ ▀▀▀▀"],
}
export const marks = "_^~"
export const marks = "_^~"

View File

@@ -25,12 +25,12 @@ export namespace UI {
export function println(...message: string[]) {
print(...message)
process.stderr.write(EOL)
Bun.stderr.write(EOL)
}
export function print(...message: string[]) {
blank = false
process.stderr.write(message.join(" "))
Bun.stderr.write(message.join(" "))
}
let blank = false
@@ -44,7 +44,7 @@ export namespace UI {
const result: string[] = []
const reset = "\x1b[0m"
const left = {
fg: "\x1b[90m",
fg: Bun.color("gray", "ansi") ?? "",
shadow: "\x1b[38;5;235m",
bg: "\x1b[48;5;235m",
}
@@ -104,13 +104,10 @@ export namespace UI {
}
export function error(message: string) {
if (message.startsWith("Error: ")) {
message = message.slice("Error: ".length)
}
println(Style.TEXT_DANGER_BOLD + "Error: " + Style.TEXT_NORMAL + message)
}
export function markdown(text: string): string {
return text
}
}
}