mirror of
https://github.com/block/goose.git
synced 2026-09-01 18:35:52 +00:00
Some checks failed
Canary / Prepare Version (push) Waiting to run
Canary / build-cli-linux (push) Blocked by required conditions
Canary / Upload Install Script (push) Waiting to run
Canary / bundle-macos-arm64 (push) Blocked by required conditions
Canary / bundle-macos-x64 (push) Blocked by required conditions
Canary / bundle-windows (push) Blocked by required conditions
Canary / bundle-windows-cuda (push) Blocked by required conditions
Canary / Release (push) Blocked by required conditions
Unused Dependencies / machete (push) Waiting to run
CI / changes (push) Waiting to run
CI / Check Rust Code Format (push) Blocked by required conditions
CI / Build and Test Rust Project (push) Blocked by required conditions
CI / Check goose-sdk UniFFI (push) Blocked by required conditions
CI / Build and Test TLS Backend (native-tls) (push) Blocked by required conditions
CI / Build and Test TLS Backend (rustls-tls) (push) Blocked by required conditions
CI / Build and Test Roaming Feature (push) Blocked by required conditions
CI / Build Rust Project on Windows (push) Waiting to run
CI / Check MSRV (push) Blocked by required conditions
CI / Lint Rust Code (push) Blocked by required conditions
CI / Check Generated Schemas are Up-to-Date (push) Blocked by required conditions
CI / Check GDK API Reference is Up-to-Date (push) Waiting to run
CI / Test and Lint Electron Desktop App (push) Blocked by required conditions
CI / Test Desktop Updater Install (ubuntu-latest) (push) Blocked by required conditions
CI / Test Desktop Updater Install (windows-latest) (push) Blocked by required conditions
MCP Conformance / Build Conformance Binaries (push) Waiting to run
MCP Conformance / Conformance 2025-11-25 / 0.1.16 (push) Blocked by required conditions
MCP Conformance / Conformance 2025-11-25 / 0.2.0-alpha.10 (push) Blocked by required conditions
MCP Conformance / Conformance 2026-07-28 / 0.2.0-alpha.10 (push) Blocked by required conditions
Create Minor Release PR / check-version-bump-pr (push) Waiting to run
Create Minor Release PR / release (push) Blocked by required conditions
Live Provider Tests / check-fork (push) Waiting to run
Live Provider Tests / changes (push) Blocked by required conditions
Live Provider Tests / Build Binary (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (Code Execution) (push) Blocked by required conditions
Live Provider Tests / Compaction Tests (push) Blocked by required conditions
Publish Docker Image / docker (push) Waiting to run
Scorecard supply-chain security / Scorecard analysis (push) Waiting to run
Buzz automation / test (push) Has been cancelled
Deploy Documentation / deploy (push) Has been cancelled
Cargo Deny / deny (push) Has been cancelled
Publish Ask AI Bot Docker Image / docker (push) Has been cancelled
268 lines
6.7 KiB
JavaScript
Executable file
268 lines
6.7 KiB
JavaScript
Executable file
#!/usr/bin/env node
|
|
|
|
import { createECDH, randomBytes } from "node:crypto";
|
|
import {
|
|
accessSync,
|
|
chmodSync,
|
|
constants,
|
|
existsSync,
|
|
mkdirSync,
|
|
readFileSync,
|
|
writeFileSync,
|
|
} from "node:fs";
|
|
import { homedir } from "node:os";
|
|
import { dirname, join } from "node:path";
|
|
import { spawnSync } from "node:child_process";
|
|
import { fileURLToPath } from "node:url";
|
|
|
|
const identityName = "Github Manager";
|
|
const relayUrl = process.env.BUZZ_RELAY_URL || "https://buzz.gdk.so";
|
|
const configHome =
|
|
process.env.XDG_CONFIG_HOME || join(homedir(), ".config");
|
|
const buzzHome =
|
|
process.env.GOOSE_BUZZ_HOME || join(configHome, "goose", "buzz");
|
|
const identityDirectory = join(buzzHome, "github-manager");
|
|
const privateKeyPath = join(identityDirectory, "private-key.nsec");
|
|
const publicKeyPath = join(identityDirectory, "public-key.npub");
|
|
const publicKeyHexPath = join(identityDirectory, "public-key.hex");
|
|
const profileCreatedPath = join(identityDirectory, "profile-created");
|
|
const keyPaths = [privateKeyPath, publicKeyPath, publicKeyHexPath];
|
|
const avatarPath = join(
|
|
dirname(fileURLToPath(import.meta.url)),
|
|
"assets",
|
|
"GithubManager.png",
|
|
);
|
|
|
|
const existingKeyPaths = keyPaths.filter(existsSync);
|
|
let nsec;
|
|
let npub;
|
|
let publicKeyHex;
|
|
if (existingKeyPaths.length > 0) {
|
|
if (existingKeyPaths.length !== keyPaths.length) {
|
|
console.error(
|
|
`Refusing to change the incomplete Github Manager key pair in ${identityDirectory}`,
|
|
);
|
|
process.exit(1);
|
|
}
|
|
|
|
chmodSync(identityDirectory, 0o700);
|
|
for (const keyPath of keyPaths) {
|
|
chmodSync(keyPath, 0o600);
|
|
}
|
|
|
|
nsec = readFileSync(privateKeyPath, "utf8").trim();
|
|
npub = readFileSync(publicKeyPath, "utf8").trim();
|
|
publicKeyHex = readFileSync(publicKeyHexPath, "utf8").trim();
|
|
console.log(`Github Manager keys already exist: ${npub}`);
|
|
console.log(`Keys: ${identityDirectory}`);
|
|
if (existsSync(profileCreatedPath)) {
|
|
chmodSync(profileCreatedPath, 0o600);
|
|
console.log("No key or profile changes were made.");
|
|
process.exit(0);
|
|
}
|
|
console.log("The profile marker is missing; recreating the Buzz profile.");
|
|
} else {
|
|
mkdirSync(identityDirectory, { recursive: true, mode: 0o700 });
|
|
chmodSync(identityDirectory, 0o700);
|
|
|
|
const { privateKey, publicKey } = generateKeyPair();
|
|
publicKeyHex = publicKey.toString("hex");
|
|
nsec = bech32Encode("nsec", privateKey);
|
|
npub = bech32Encode("npub", publicKey);
|
|
|
|
writeSecret(privateKeyPath, nsec);
|
|
writeSecret(publicKeyPath, npub);
|
|
writeSecret(publicKeyHexPath, publicKeyHex);
|
|
|
|
console.log(`Generated ${identityName}: ${npub}`);
|
|
console.log(`Public key (hex): ${publicKeyHex}`);
|
|
console.log(`Keys: ${identityDirectory}`);
|
|
}
|
|
|
|
const buzz = findBuzz();
|
|
const buzzEnvironment = {
|
|
...process.env,
|
|
BUZZ_PRIVATE_KEY: nsec,
|
|
};
|
|
const uploadResult = spawnSync(
|
|
buzz,
|
|
["--relay", relayUrl, "upload", "file", "--file", avatarPath],
|
|
{
|
|
encoding: "utf8",
|
|
env: buzzEnvironment,
|
|
},
|
|
);
|
|
|
|
if (uploadResult.error) {
|
|
console.error("The Github Manager avatar was not uploaded.");
|
|
console.error(`Could not run Buzz: ${uploadResult.error.message}`);
|
|
process.exit(1);
|
|
}
|
|
|
|
if (uploadResult.status !== 0) {
|
|
console.error("The Github Manager avatar was not uploaded.");
|
|
process.stderr.write(uploadResult.stderr);
|
|
process.exit(uploadResult.status || 1);
|
|
}
|
|
|
|
let avatarUrl;
|
|
try {
|
|
avatarUrl = JSON.parse(uploadResult.stdout).url;
|
|
} catch {
|
|
console.error("Buzz returned an invalid response after uploading the avatar.");
|
|
process.exit(1);
|
|
}
|
|
|
|
if (!avatarUrl) {
|
|
console.error("Buzz did not return a URL for the uploaded avatar.");
|
|
process.exit(1);
|
|
}
|
|
|
|
const profileResult = spawnSync(
|
|
buzz,
|
|
[
|
|
"--relay",
|
|
relayUrl,
|
|
"users",
|
|
"set-profile",
|
|
"--name",
|
|
identityName,
|
|
"--avatar",
|
|
avatarUrl,
|
|
],
|
|
{
|
|
encoding: "utf8",
|
|
env: buzzEnvironment,
|
|
},
|
|
);
|
|
|
|
if (profileResult.error) {
|
|
console.error("The Buzz profile was not created.");
|
|
console.error(`Could not run Buzz: ${profileResult.error.message}`);
|
|
process.exit(1);
|
|
}
|
|
|
|
if (profileResult.status !== 0) {
|
|
console.error("The Buzz profile was not created.");
|
|
process.stderr.write(profileResult.stderr);
|
|
process.exit(profileResult.status || 1);
|
|
}
|
|
|
|
writeSecret(profileCreatedPath, new Date().toISOString());
|
|
|
|
if (profileResult.stdout) {
|
|
process.stdout.write(profileResult.stdout);
|
|
}
|
|
console.log(`Created the ${identityName} Buzz profile.`);
|
|
|
|
function writeSecret(path, value) {
|
|
writeFileSync(path, `${value}\n`, { flag: "wx", mode: 0o600 });
|
|
chmodSync(path, 0o600);
|
|
}
|
|
|
|
function generateKeyPair() {
|
|
while (true) {
|
|
const privateKey = randomBytes(32);
|
|
const ecdh = createECDH("secp256k1");
|
|
|
|
try {
|
|
ecdh.setPrivateKey(privateKey);
|
|
const compressedPublicKey = ecdh.getPublicKey(undefined, "compressed");
|
|
return {
|
|
privateKey,
|
|
publicKey: compressedPublicKey.subarray(1),
|
|
};
|
|
} catch {
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
function findBuzz() {
|
|
if (process.env.BUZZ_BIN) {
|
|
return process.env.BUZZ_BIN;
|
|
}
|
|
|
|
const bundledBuzz = "/Applications/Buzz.app/Contents/MacOS/buzz";
|
|
try {
|
|
accessSync(bundledBuzz, constants.X_OK);
|
|
return bundledBuzz;
|
|
} catch {
|
|
return "buzz";
|
|
}
|
|
}
|
|
|
|
function bech32Encode(prefix, bytes) {
|
|
const alphabet = "qpzry9x8gf2tvdw0s3jn54khce6mua7l";
|
|
const words = convertBits(bytes, 8, 5);
|
|
const checksumInput = [
|
|
...expandPrefix(prefix),
|
|
...words,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
];
|
|
const checksum = polymod(checksumInput) ^ 1;
|
|
const checksumWords = Array.from(
|
|
{ length: 6 },
|
|
(_, index) => (checksum >> (5 * (5 - index))) & 31,
|
|
);
|
|
|
|
return `${prefix}1${[...words, ...checksumWords]
|
|
.map((word) => alphabet[word])
|
|
.join("")}`;
|
|
}
|
|
|
|
function convertBits(bytes, fromBits, toBits) {
|
|
let accumulator = 0;
|
|
let bitCount = 0;
|
|
const result = [];
|
|
const mask = (1 << toBits) - 1;
|
|
|
|
for (const byte of bytes) {
|
|
accumulator = (accumulator << fromBits) | byte;
|
|
bitCount += fromBits;
|
|
|
|
while (bitCount >= toBits) {
|
|
bitCount -= toBits;
|
|
result.push((accumulator >> bitCount) & mask);
|
|
}
|
|
}
|
|
|
|
if (bitCount > 0) {
|
|
result.push((accumulator << (toBits - bitCount)) & mask);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
function expandPrefix(prefix) {
|
|
return [
|
|
...Array.from(prefix, (character) => character.charCodeAt(0) >> 5),
|
|
0,
|
|
...Array.from(prefix, (character) => character.charCodeAt(0) & 31),
|
|
];
|
|
}
|
|
|
|
function polymod(values) {
|
|
const generators = [
|
|
0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3,
|
|
];
|
|
let checksum = 1;
|
|
|
|
for (const value of values) {
|
|
const top = checksum >>> 25;
|
|
checksum = ((checksum & 0x1ffffff) << 5) ^ value;
|
|
|
|
for (let index = 0; index < generators.length; index += 1) {
|
|
if ((top >> index) & 1) {
|
|
checksum ^= generators[index];
|
|
}
|
|
}
|
|
}
|
|
|
|
return checksum;
|
|
}
|