Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 3 additions & 14 deletions github/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -853,21 +853,10 @@ async function createPR(base: string, branch: string, title: string, body: strin
})
return pr.data.number
}
function footer(opts?: { image?: boolean }) {
const { providerID, modelID } = useEnvModel()

const image = (() => {
if (!shareUrl) return ""
if (!opts?.image) return ""

const titleAlt = encodeURIComponent(session.title.substring(0, 50))
const title64 = Buffer.from(session.title.substring(0, 700), "utf8").toString("base64")
const shareId = shareUrl.split("/").pop()

return `<a href="${shareUrl}"><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`
})()
// Shares live on Redrob Chat; the opencode social-cards image is no longer requested.
function footer(_opts?: { image?: boolean }) {
const shareLink = shareUrl ? `[Redrob Code session](${shareUrl})&nbsp;&nbsp;|&nbsp;&nbsp;` : ""
return `\n\n${image}${shareLink}[github run](${useEnvRunUrl()})`
return `\n\n${shareLink}[github run](${useEnvRunUrl()})`
}

async function fetchRepo() {
Expand Down
2 changes: 2 additions & 0 deletions packages/core/src/flag/flag.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ export const Flag = {
* still wants a key it recognises.
*/
REDROB_CONSOLE_URL: process.env["REDROB_CONSOLE_URL"],
/** Point sharing at a different Redrob Chat API (staging, local). Wins over `chat.url` in config. */
REDROB_CHAT_API_URL: process.env["REDROB_CHAT_API_URL"],
REDROB_CONFIG: process.env["REDROB_CONFIG"],
REDROB_CONFIG_CONTENT: process.env["REDROB_CONFIG_CONTENT"],
REDROB_DISABLE_AUTOUPDATE: truthy("REDROB_DISABLE_AUTOUPDATE"),
Expand Down
7 changes: 7 additions & 0 deletions packages/core/src/v1/config/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,13 @@ export const Info = Schema.Struct({
enterprise: Schema.optional(
Schema.Struct({ url: Schema.optional(Schema.String).annotate({ description: "Enterprise URL" }) }),
),
chat: Schema.optional(
Schema.Struct({
url: Schema.optional(Schema.String).annotate({
description: "Redrob Chat API base URL used for sharing (default https://chat-prod.redrob.ai)",
}),
}),
),
tool_output: Schema.optional(
Schema.Struct({
max_lines: Schema.optional(PositiveInt).annotate({
Expand Down
10 changes: 10 additions & 0 deletions packages/redrob/schema/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -1202,6 +1202,16 @@
},
"additionalProperties": false
},
"chat": {
"type": "object",
"properties": {
"url": {
"type": "string",
"description": "Redrob Chat API base URL used for sharing (default https://chat-prod.redrob.ai)"
}
},
"additionalProperties": false
},
"tool_output": {
"type": "object",
"properties": {
Expand Down
16 changes: 4 additions & 12 deletions packages/redrob/src/cli/cmd/github.handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1220,19 +1220,11 @@ export const githubRun = Effect.fn("Cli.github.run")(function* (args: { event?:
}
}

function footer(opts?: { image?: boolean }) {
const image = (() => {
if (!shareUrl) return ""
if (!opts?.image) return ""

const titleAlt = encodeURIComponent(session.title.substring(0, 50))
const title64 = Buffer.from(session.title.substring(0, 700), "utf8").toString("base64")
const shareId = shareUrl.split("/").pop()

return `<a href="${shareUrl}"><img width="200" alt="${titleAlt}" src="https://social-cards.sst.dev/redrob-share/${title64}.png?model=${providerID}/${modelID}&version=${session.version}&id=${shareId}" /></a>\n`
})()
// The share card image was rendered by opencode's social-cards service from the session title. Shares
// now live on Redrob Chat, so the footer carries the plain link and nothing is sent to that service.
function footer(_opts?: { image?: boolean }) {
const shareLink = shareUrl ? `[redrob session](${shareUrl})&nbsp;&nbsp;|&nbsp;&nbsp;` : ""
return `\n\n${image}${shareLink}[github run](${runUrl})`
return `\n\n${shareLink}[github run](${runUrl})`
}

async function fetchRepo() {
Expand Down
63 changes: 63 additions & 0 deletions packages/redrob/src/cli/cmd/share.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import path from "path"
import { Effect } from "effect"
import { effectCmd, fail } from "../effect-cmd"
import { ShareNext } from "@/share/share-next"
import { UI } from "../ui"

/** Redrob Chat refuses bodies over 2 MB; checked here so the user gets a clear message, not a 413. */
const MAX_BYTES = 2 * 1024 * 1024

function format(file: string): { format: ShareNext.ArtifactInput["format"]; language?: string } {
const ext = path.extname(file).slice(1).toLowerCase()
if (ext === "md" || ext === "markdown" || ext === "mdx") return { format: "markdown" }
if (ext === "html" || ext === "htm") return { format: "html" }
if (ext === "json") return { format: "json" }
return { format: "code", language: ext || undefined }
}

/** `--lines 10-40` (1-based, inclusive) or `--lines 12` for one line. */
function slice(text: string, lines?: string) {
if (!lines) return text
const match = lines.match(/^(\d+)(?:-(\d+))?$/)
if (!match) return undefined
const start = Number(match[1])
const end = match[2] ? Number(match[2]) : start
if (start < 1 || end < start) return undefined
return text.split("\n").slice(start - 1, end).join("\n")
}

export const ShareCommand = effectCmd({
command: "share <file>",
describe: "share a file or snippet to Redrob Chat",
builder: (yargs) =>
yargs
.positional("file", {
describe: "file to share",
type: "string",
demandOption: true,
})
.option("lines", {
describe: "only share these lines, e.g. 10-40",
type: "string",
})
.option("title", {
describe: "title shown on the share page",
type: "string",
}),
handler: Effect.fn("Cli.share")(function* (args) {
const file = path.resolve(args.file)
const text = yield* Effect.tryPromise(() => Bun.file(file).text()).pipe(
Effect.catch(() => fail(`Cannot read ${args.file}`)),
)
const body = slice(text, args.lines)
if (body === undefined) return yield* fail(`Invalid --lines value: ${args.lines}`)
if (Buffer.byteLength(body, "utf8") > MAX_BYTES) return yield* fail("File is larger than 2 MB")

const name = path.basename(file)
const title = args.title ?? (args.lines ? `${name}:${args.lines}` : name)
const result = yield* ShareNext.Service.use((svc) => svc.artifact({ title, body, ...format(file) })).pipe(
Effect.catch((error) => fail(error instanceof Error ? error.message : String(error))),
)
UI.println(UI.Style.TEXT_INFO_BOLD + "~ " + result.url + UI.Style.TEXT_NORMAL)
}),
})
2 changes: 2 additions & 0 deletions packages/redrob/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { McpCommand } from "./cli/cmd/mcp"
import { GithubCommand } from "./cli/cmd/github"
import { ExportCommand } from "./cli/cmd/export"
import { ImportCommand } from "./cli/cmd/import"
import { ShareCommand } from "./cli/cmd/share"
import { AttachCommand } from "./cli/cmd/attach"
import { TuiThreadCommand } from "./cli/cmd/tui"
import { AcpCommand } from "./cli/cmd/acp"
Expand Down Expand Up @@ -102,6 +103,7 @@ const cli = yargs(args)
.command(StatsCommand)
.command(ExportCommand)
.command(ImportCommand)
.command(ShareCommand)
.command(GithubCommand)
.command(PrCommand)
.command(SessionCommand)
Expand Down
Loading
Loading