proof-of-persona
Version:
Cryptographically verified digital personalities with online reputations and LLM-style thinking.
1,243 lines (1,198 loc) • 43.9 kB
JavaScript
;
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/cli/index.ts
var index_exports = {};
__export(index_exports, {
printHelp: () => printHelp
});
module.exports = __toCommonJS(index_exports);
// src/cli/create.ts
var import_ethers2 = require("ethers");
// src/core/persona.ts
var import_uuid = require("uuid");
// src/storage/local.ts
var import_fs = __toESM(require("fs"));
var import_path = __toESM(require("path"));
// src/core/signer.ts
var import_ethers = require("ethers");
async function signThought(thought, privateKey) {
try {
const wallet = new import_ethers.Wallet(privateKey);
const message = JSON.stringify({
content: thought.content,
topic: thought.topic,
timestamp: thought.timestamp
});
return await wallet.signMessage(message);
} catch (error) {
console.error("\u{1F6D1} signThought failed. Invalid private key:", privateKey);
throw error;
}
}
async function verifyThought(thought, signature, expectedAddress) {
const message = JSON.stringify({
content: thought.content,
topic: thought.topic,
timestamp: thought.timestamp
});
const recoveredAddress = import_ethers.utils.verifyMessage(message, signature);
return recoveredAddress.toLowerCase() === expectedAddress.toLowerCase();
}
async function signPersona(persona, privateKey) {
const wallet = new import_ethers.Wallet(privateKey);
const message = JSON.stringify({
id: persona.id,
alias: persona.alias,
publicKey: persona.publicKey,
behaviorModel: persona.behaviorModel,
createdAt: persona.createdAt
});
return await wallet.signMessage(message);
}
function verifyPersona(persona, signature, expectedAddress) {
const message = JSON.stringify({
id: persona.id,
alias: persona.alias,
publicKey: persona.publicKey,
behaviorModel: persona.behaviorModel,
createdAt: persona.createdAt
});
const recovered = import_ethers.utils.verifyMessage(message, signature);
return recovered.toLowerCase() === expectedAddress.toLowerCase();
}
function verifyFullPersona(persona) {
if (!persona.signature) return false;
return verifyPersona(persona, persona.signature, persona.publicKey);
}
// src/storage/config.ts
var defaultPaths = {
personas: process.env.PERSONA_STORAGE_PATH || "storage/personas.json",
thoughts: process.env.THOUGHTS_STORAGE_PATH || "storage/thoughts.json"
};
var currentPaths = { ...defaultPaths };
function getStoragePaths() {
return currentPaths;
}
// src/storage/local.ts
function ensureFile(filePath) {
const dir = import_path.default.dirname(filePath);
if (!import_fs.default.existsSync(dir)) import_fs.default.mkdirSync(dir, { recursive: true });
if (!import_fs.default.existsSync(filePath)) import_fs.default.writeFileSync(filePath, "[]");
}
function readJson(filePath) {
ensureFile(filePath);
return JSON.parse(import_fs.default.readFileSync(filePath, "utf-8"));
}
function writeJson(filePath, data) {
import_fs.default.writeFileSync(filePath, JSON.stringify(data, null, 2));
}
function writePersonaToStorage(persona) {
const { personas } = getStoragePaths();
const list = readJson(personas);
const exists = list.some((p) => p.id === persona.id);
if (exists) return;
list.push(persona);
writeJson(personas, list);
}
function loadPersonas({ onlyValid = true } = {}) {
const { personas } = getStoragePaths();
const all = readJson(personas);
return onlyValid ? all.filter(verifyFullPersona) : all;
}
function writeThoughtToStorage(thought) {
const { thoughts } = getStoragePaths();
const list = readJson(thoughts);
list.push(thought);
writeJson(thoughts, list);
}
function loadThoughts() {
const { thoughts } = getStoragePaths();
return readJson(thoughts);
}
// src/core/persona.ts
async function createPersona({
alias,
publicKey,
behaviorModel,
style,
bio,
tags,
avatarHash,
metadata,
privateKey
}) {
const existing = loadPersonas();
if (!privateKey) throw new Error("Private key is required to sign persona.");
if (existing.find((p) => p.alias.toLowerCase() === alias.toLowerCase())) {
throw new Error(`\u274C Persona with alias "${alias}" already exists.`);
}
if (existing.find((p) => p.publicKey.toLowerCase() === publicKey.toLowerCase())) {
throw new Error(`\u274C Persona with public key "${publicKey}" already exists.`);
}
const basePersona = {
id: (0, import_uuid.v4)(),
alias,
publicKey,
behaviorModel,
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
style,
bio,
tags,
avatarHash,
metadata,
revoked: false
};
const signature = privateKey ? await signPersona(basePersona, privateKey) : void 0;
const persona = {
...basePersona,
signature
};
writePersonaToStorage(persona);
return persona;
}
// src/cli/create.ts
var dotenv = __toESM(require("dotenv"));
var import_readline = __toESM(require("readline"));
dotenv.config();
var BEHAVIOR_MODELS = [
"socratic-punk",
"cypherpunk-minimalist",
"zen-daoist",
"rational-skeptic",
"dreamer-optimist"
];
async function createCommand() {
const PRIVATE_KEY = process.env.PRIVATE_KEY;
if (!PRIVATE_KEY) return log("\u274C PRIVATE_KEY not set in .env");
const wallet = new import_ethers2.Wallet(PRIVATE_KEY);
const verified = await confirmOwnership(PRIVATE_KEY, wallet.address);
if (!verified) return log("\u274C Ownership verification failed. Aborting.");
let alias = process.argv[3];
while (!alias) alias = await ask("\u{1F9E0} Enter alias for your persona: ");
alias = alias.trim();
log("\nAvailable Behavior Models:");
BEHAVIOR_MODELS.forEach((m, i) => log(` ${i + 1}. ${m}`));
const selected = await ask("Select behavior model [1\u20135]: ");
const behaviorModel = BEHAVIOR_MODELS[parseInt(selected) - 1] || BEHAVIOR_MODELS[0];
let style = "";
while (!style) {
style = (await ask("\u{1F3A8} Enter style (e.g. 'stoic', 'humorous'): ")).trim();
if (!style) log("\u26A0\uFE0F Style is required.");
}
const bio = await ask("\u{1F4C4} Short bio (optional): ");
const tags = (await ask("\u{1F3F7}\uFE0F Tags (comma-separated, optional): ")).split(",").map((t) => t.trim()).filter(Boolean);
const avatarHash = await ask("\u{1F5BC}\uFE0F Avatar IPFS hash (optional): ");
try {
const persona = await createPersona({
alias,
publicKey: wallet.address,
behaviorModel,
style,
bio: bio || void 0,
tags: tags.length ? tags : void 0,
avatarHash: avatarHash || void 0,
metadata: { createdVia: "CLI" },
privateKey: PRIVATE_KEY
});
log(`
\u2705 Persona created: ${persona.alias}`);
log(`\u{1F9FE} Address: ${wallet.address}`);
} catch (err) {
log(`\u274C ${err.message}`);
}
}
function ask(q) {
const rl = import_readline.default.createInterface({ input: process.stdin, output: process.stdout });
return new Promise((resolve) => rl.question(q, (a) => {
rl.close();
resolve(a);
}));
}
async function confirmOwnership(privateKey, publicKey) {
try {
const wallet = new import_ethers2.Wallet(privateKey);
const msg = `create-persona:${Date.now()}`;
const sig = await wallet.signMessage(msg);
const recovered = import_ethers2.utils.verifyMessage(msg, sig);
return recovered.toLowerCase() === publicKey.toLowerCase();
} catch {
return false;
}
}
function log(msg) {
console.log(msg);
}
// src/cli/post.ts
var import_ethers3 = require("ethers");
// src/core/thoughts.ts
async function postThought(persona, {
content,
topic
}) {
const thought = {
content,
topic,
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
author: persona.alias
};
const privateKey = process.env.PRIVATE_KEY;
if (!privateKey) throw new Error("\u274C PRIVATE_KEY not set");
const signature = await signThought(thought, privateKey);
thought.signature = signature;
writeThoughtToStorage(thought);
return thought;
}
// src/utils/config.ts
var import_fs2 = __toESM(require("fs"));
var import_path2 = __toESM(require("path"));
var CONFIG_FILE = import_path2.default.resolve(".persona-config.json");
function getActivePersonaAlias() {
if (!import_fs2.default.existsSync(CONFIG_FILE)) return null;
const json = JSON.parse(import_fs2.default.readFileSync(CONFIG_FILE, "utf-8"));
return json.activePersona || null;
}
// src/cli/post.ts
var dotenv2 = __toESM(require("dotenv"));
dotenv2.config();
async function postCommand() {
const PRIVATE_KEY = process.env.PRIVATE_KEY;
if (!PRIVATE_KEY) {
console.error("\u274C PRIVATE_KEY not set in .env");
return;
}
const wallet = new import_ethers3.Wallet(PRIVATE_KEY);
const args = process.argv.slice(3);
const content = args[0];
if (!content) {
console.error('\u274C Please provide content: persona post "your text" --topic=...');
return;
}
const topic = args.find((arg) => arg.startsWith("--topic="))?.split("=")[1] || "general";
const activeAlias = getActivePersonaAlias();
if (!activeAlias) {
console.error("\u274C No active persona. Use: persona use <alias>");
return;
}
const personas = loadPersonas();
const persona = personas.find((p) => p.alias === activeAlias);
if (!persona) {
console.error(`\u274C Persona "${activeAlias}" not found in local storage.`);
return;
}
const thought = await postThought(persona, { content, topic });
console.log("\u{1F9E0} Posted:", thought.content);
console.log("\u{1F58A} Signature:", thought.signature?.slice(0, 16), "...");
}
// src/cli/verify.ts
var import_fs3 = __toESM(require("fs"));
async function verifyCommand() {
const thoughtsFile4 = "./storage/thoughts.json";
if (!import_fs3.default.existsSync(thoughtsFile4)) {
console.error("\u274C No thoughts found.");
return;
}
const thoughts = JSON.parse(import_fs3.default.readFileSync(thoughtsFile4, "utf-8"));
const last = thoughts.at(-1);
if (!last) {
console.error("\u274C No thoughts to verify.");
return;
}
const activeAlias = getActivePersonaAlias();
if (!activeAlias) {
console.error("\u274C No active persona. Use: persona use <alias>");
return;
}
const personas = loadPersonas();
const persona = personas.find((p) => p.alias === activeAlias);
if (!persona) {
console.error(`\u274C Active persona "${activeAlias}" not found.`);
return;
}
const isValid = await verifyThought(
{
content: last.content,
topic: last.topic,
timestamp: last.timestamp
},
last.signature,
persona.publicKey
);
console.log("\u{1F9FE} Thought:", last.content);
console.log("\u{1F510} Signature valid:", isValid);
}
// src/cli/list.ts
var dotenv3 = __toESM(require("dotenv"));
dotenv3.config();
async function listCommand() {
const activeAlias = getActivePersonaAlias();
if (!activeAlias) {
console.error("\u274C No active persona. Use: persona use <alias>");
return;
}
const thoughts = loadThoughts();
if (!thoughts.length) {
console.log("\u274C No thoughts found.");
return;
}
const validThoughts = thoughts.filter(
(t) => t?.content && t?.topic && t?.timestamp && t?.signature && t?.author === activeAlias
);
if (!validThoughts.length) {
console.log(`\u274C No thoughts found for active persona "${activeAlias}".`);
return;
}
console.log(`\u{1F9E0} Found ${validThoughts.length} thought(s) by "${activeAlias}":
`);
validThoughts.forEach((t, i) => {
console.log(`#${i + 1}`);
console.log(`\u{1F5EF}\uFE0F ${t.content}`);
console.log(`\u{1F4DA} Topic: ${t.topic}`);
console.log(`\u{1F552} Timestamp: ${t.timestamp}`);
console.log(`\u{1F58A} Author: ${t.author}`);
console.log(`\u{1F510} Signature: ${t.signature?.slice(0, 16)}...
`);
});
}
// src/cli/clean.ts
var import_fs4 = __toESM(require("fs"));
var import_path3 = __toESM(require("path"));
var dotenv4 = __toESM(require("dotenv"));
dotenv4.config();
var thoughtsFile = import_path3.default.resolve("storage", "thoughts.json");
function isValidThought(t) {
return typeof t?.content === "string" && typeof t?.topic === "string" && typeof t?.timestamp === "string" && typeof t?.author === "string" && typeof t?.signature === "string";
}
function cleanCommand() {
if (!import_fs4.default.existsSync(thoughtsFile)) {
console.log("\u274C No thoughts.json found.");
return;
}
const raw = import_fs4.default.readFileSync(thoughtsFile, "utf-8");
const thoughts = JSON.parse(raw);
const validThoughts = thoughts.filter(isValidThought);
const removed = thoughts.length - validThoughts.length;
import_fs4.default.writeFileSync(thoughtsFile, JSON.stringify(validThoughts, null, 2));
console.log(`\u{1F9F9} Cleaned thoughts.json: removed ${removed} invalid record(s).`);
}
// src/cli/export.ts
var import_fs5 = __toESM(require("fs"));
var import_path4 = __toESM(require("path"));
var import_archiver = __toESM(require("archiver"));
var storageDir = import_path4.default.resolve("storage");
var exportDir = import_path4.default.resolve("export");
var thoughtsFile2 = import_path4.default.join(storageDir, "thoughts.json");
var personasFile = import_path4.default.join(storageDir, "personas.json");
function exportCommand() {
const target = process.argv[3];
const zip = process.argv.includes("--zip");
const format = process.argv.includes("--format=txt") ? "txt" : "json";
const onlyActive = process.argv.includes("--only-active");
if (target === "demo") {
const demoType = process.argv[4];
if (demoType === "thoughts" || demoType === "personas") {
generateDemoFile(demoType);
} else {
console.log("\u274C Please specify 'thoughts' or 'personas' for demo export.");
}
return;
}
if (!target || target !== "thoughts" && target !== "personas") {
console.log(`\u274C Usage: persona export [thoughts|personas] [--zip] [--format=txt] [--only-active]`);
return;
}
const sourceFile = target === "thoughts" ? thoughtsFile2 : personasFile;
if (!import_fs5.default.existsSync(sourceFile)) {
console.log(`\u274C No ${target} data found.`);
return;
}
let exportData = JSON.parse(import_fs5.default.readFileSync(sourceFile, "utf-8"));
if (onlyActive && target === "thoughts") {
const activeAlias = getActivePersonaAlias();
exportData = exportData.filter((t) => t.author === activeAlias);
}
if (!import_fs5.default.existsSync(exportDir)) import_fs5.default.mkdirSync(exportDir);
const outFile = import_path4.default.join(exportDir, `${target}-export.${format}`);
const content = format === "txt" ? exportData.map((item) => JSON.stringify(item)).join("\n") : JSON.stringify(exportData, null, 2);
import_fs5.default.writeFileSync(outFile, content);
console.log(`\u2705 Exported ${exportData.length} ${target} to ${outFile}`);
if (zip) {
const zipPath = import_path4.default.join(exportDir, `${target}-export.zip`);
const output = import_fs5.default.createWriteStream(zipPath);
const archive = (0, import_archiver.default)("zip", { zlib: { level: 9 } });
archive.pipe(output);
archive.file(outFile, { name: import_path4.default.basename(outFile) });
archive.finalize();
output.on("close", () => {
console.log(`\u{1F4E6} Zipped to ${zipPath} (${archive.pointer()} bytes)`);
});
}
}
function generateDemoFile(type) {
if (!import_fs5.default.existsSync(exportDir)) import_fs5.default.mkdirSync(exportDir);
const filename = import_path4.default.join(exportDir, `${type}-demo.json`);
const demoContent = type === "thoughts" ? [{
content: "Crypto is the new language of freedom.",
topic: "philosophy",
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
author: "DemoPersona",
signature: "0xSIGNATURE"
}] : [{
alias: "DemoPersona",
publicKey: "0xPUBLIC_KEY",
behaviorModel: "philosopher",
id: "demo-id",
createdAt: (/* @__PURE__ */ new Date()).toISOString()
}];
import_fs5.default.writeFileSync(filename, JSON.stringify(demoContent, null, 2));
console.log(`\u{1F9EA} Demo ${type} exported to ${filename}`);
}
// src/core/memory.ts
var import_fs6 = __toESM(require("fs"));
var import_path5 = __toESM(require("path"));
var logPath = import_path5.default.resolve("storage", "memory.log");
function memoryLog(text) {
import_fs6.default.appendFileSync(logPath, `
[${(/* @__PURE__ */ new Date()).toISOString()}] ${text}`);
}
function memoryShow() {
if (!import_fs6.default.existsSync(logPath)) return [];
return import_fs6.default.readFileSync(logPath, "utf-8").split("\n").filter(Boolean);
}
function memoryClear() {
if (import_fs6.default.existsSync(logPath)) {
import_fs6.default.unlinkSync(logPath);
}
}
// src/cli/memory.ts
function memoryCommand() {
const action = process.argv[3];
const content = process.argv.slice(4).join(" ");
switch (action) {
case "log":
memoryLog(content);
console.log("\u{1F9E0} Logged:", content);
break;
case "show":
const logs = memoryShow();
if (logs.length === 0) {
console.log("\u{1F573} Memory is empty.");
} else {
console.log("\u{1F4DC} Memory Log:");
logs.forEach((line, i) => {
console.log(`#${i + 1} ${line}`);
});
}
break;
case "clear":
memoryClear();
console.log("\u{1F5D1} Memory cleared.");
break;
default:
console.log(`
\u{1F4D8} Memory commands:
persona memory log "your thought" # Add entry to memory
persona memory show # Show all memory log
persona memory clear # Clear memory log
`);
}
}
// src/cli/import.ts
var import_fs7 = __toESM(require("fs"));
var import_path6 = __toESM(require("path"));
// src/utils/validate.ts
function isValidThought2(obj) {
return typeof obj.content === "string" && typeof obj.topic === "string" && typeof obj.timestamp === "string" && typeof obj.author === "string" && typeof obj.signature === "string";
}
function isValidPersona(obj) {
return typeof obj.alias === "string" && typeof obj.publicKey === "string" && typeof obj.behaviorModel === "string";
}
// src/cli/import.ts
var storageDir2 = import_path6.default.resolve("storage");
var thoughtsFile3 = import_path6.default.join(storageDir2, "thoughts.json");
var personasFile2 = import_path6.default.join(storageDir2, "personas.json");
async function importCommand() {
const filePath = process.argv[3];
const replace = process.argv.includes("--replace");
if (!filePath) {
console.error("\u274C Please provide a path to the file. Example: persona import thoughts.json");
return;
}
if (!import_fs7.default.existsSync(filePath)) {
console.error(`\u274C File not found: ${filePath}`);
return;
}
if (!filePath.endsWith(".json")) {
console.error("\u274C Only .json files are supported.");
return;
}
let parsed;
try {
parsed = JSON.parse(import_fs7.default.readFileSync(filePath, "utf-8"));
} catch {
console.error("\u274C Failed to parse JSON. Make sure the file is valid.");
return;
}
if (!Array.isArray(parsed)) {
console.error("\u274C JSON file must contain an array.");
return;
}
const name = import_path6.default.basename(filePath).toLowerCase();
const isThought = name.includes("thought");
const isPersona = name.includes("persona");
if (!isThought && !isPersona) {
console.error("\u274C File name must include either 'thought' or 'persona' to detect data type.");
return;
}
let valid = parsed.filter(
(entry) => isThought ? isValidThought2(entry) : isValidPersona(entry)
);
if (isThought && valid.length) {
const personas = import_fs7.default.existsSync(personasFile2) ? JSON.parse(import_fs7.default.readFileSync(personasFile2, "utf-8")) : [];
valid = await Promise.all(
valid.map(async (thought) => {
const author = thought.author;
const found = personas.find((p) => p.alias === author);
if (!found) return null;
const isVerified = await verifyThought(
{
content: thought.content,
topic: thought.topic,
timestamp: thought.timestamp
},
thought.signature,
found.publicKey
);
return isVerified ? thought : null;
})
).then((results) => results.filter(Boolean));
}
if (!valid.length) {
console.error("\u274C No valid entries found in file.");
return;
}
const targetFile = isThought ? thoughtsFile3 : personasFile2;
const label = isThought ? "thoughts" : "personas";
if (!import_fs7.default.existsSync(storageDir2)) import_fs7.default.mkdirSync(storageDir2);
if (replace) {
import_fs7.default.writeFileSync(targetFile, JSON.stringify(valid, null, 2));
console.log(`\u2705 Imported ${valid.length} ${label} (replaced existing data).`);
} else {
const existing = import_fs7.default.existsSync(targetFile) ? JSON.parse(import_fs7.default.readFileSync(targetFile, "utf-8")) : [];
const merged = [...existing, ...valid];
import_fs7.default.writeFileSync(targetFile, JSON.stringify(merged, null, 2));
console.log(`\u2705 Imported ${valid.length} ${label} (merged with existing).`);
}
}
// src/cli/init.ts
var import_ethers4 = require("ethers");
var import_readline2 = __toESM(require("readline"));
var dotenv5 = __toESM(require("dotenv"));
dotenv5.config();
var BEHAVIOR_MODELS2 = [
"socratic-punk",
"cypherpunk-minimalist",
"zen-daoist",
"rational-skeptic",
"dreamer-optimist"
];
async function initCommand() {
intro();
const PRIVATE_KEY = process.env.PRIVATE_KEY;
if (!PRIVATE_KEY) return logError("PRIVATE_KEY not set in .env");
const wallet = new import_ethers4.Wallet(PRIVATE_KEY);
const verified = await confirmOwnership2(PRIVATE_KEY, wallet.address);
if (!verified) return logError("Key verification failed. Public and private keys do not match.");
log2("\u{1F510} Key ownership verified.\n");
const existing = loadPersonas();
const alias = await chooseUniqueAlias(existing);
const behaviorModel = await selectFromList("Select behavior model", BEHAVIOR_MODELS2, 1);
let style = "";
while (!style) {
style = (await ask2("\u{1F3A8} Enter style (e.g. 'neutral', 'chaotic'): ")).trim();
if (!style) log2("\u26A0\uFE0F Style is required.");
}
const bio = await ask2("\u{1F4C4} Short bio (optional): ");
const avatarHash = await ask2("\u{1F5BC}\uFE0F Avatar IPFS hash (optional): ");
const tagsInput = await ask2("\u{1F3F7}\uFE0F Tags (comma-separated, optional): ");
const tags = tagsInput.split(",").map((t) => t.trim()).filter(Boolean);
confirmSummary({ alias, behaviorModel, style, wallet, bio, tags, avatarHash });
const proceed = await ask2("Proceed? (y/n): ");
if (proceed.toLowerCase() !== "y") return log2("\u274C Cancelled.");
try {
const persona = await createPersona({
alias,
publicKey: wallet.address,
behaviorModel,
style,
bio: bio || void 0,
avatarHash: avatarHash || void 0,
tags: tags.length ? tags : void 0,
metadata: { createdVia: "init" },
privateKey: PRIVATE_KEY
});
log2(`
\u2705 Persona created: ${persona.alias}`);
log2(`\u{1F9FE} Address: ${wallet.address}`);
log2(`\u{1F3A8} Style: ${persona.style}`);
log2(`\u{1F9E0} Model: ${persona.behaviorModel}`);
log2(`\u{1F4C4} Bio: ${persona.bio || "\u041D\u0435 \u0443\u043A\u0430\u0437\u0430\u043D\u043E"}`);
log2(`\u{1F3F7}\uFE0F Tags: ${persona.tags?.join(", ") || "\u041D\u0435 \u0443\u043A\u0430\u0437\u0430\u043D\u043E"}`);
log2(`\u{1F5BC}\uFE0F Avatar: ${persona.avatarHash || "\u041D\u0435 \u0443\u043A\u0430\u0437\u0430\u043D\u043E"}`);
} catch (err) {
logError(err.message || "Unexpected error.");
}
}
function ask2(q) {
const rl = import_readline2.default.createInterface({ input: process.stdin, output: process.stdout });
return new Promise((resolve) => rl.question(q, (a) => {
rl.close();
resolve(a);
}));
}
async function confirmOwnership2(privateKey, publicKey) {
try {
const wallet = new import_ethers4.Wallet(privateKey);
const msg = `create-persona:${Date.now()}`;
const sig = await wallet.signMessage(msg);
const recovered = import_ethers4.utils.verifyMessage(msg, sig);
return recovered.toLowerCase() === publicKey.toLowerCase();
} catch {
return false;
}
}
async function chooseUniqueAlias(existing) {
while (true) {
const input = (await ask2("\u{1F9E0} Choose alias (empty for random): ")).trim();
const alias = input || generateRandomAlias();
const taken = existing.some((p) => p.alias.toLowerCase() === alias.toLowerCase());
if (!taken) return alias;
log2(`\u26A0\uFE0F Alias "${alias}" is already taken. Try again.
`);
}
}
async function selectFromList(prompt, items, defaultIndex = 0) {
log2(`
${prompt}:`);
items.forEach((item, i2) => log2(` ${i2 + 1}. ${item}`));
const i = parseInt(await ask2(`
[1-${items.length}]: `)) - 1;
return items[i] || items[defaultIndex];
}
function generateRandomAlias() {
const base = ["neo", "geist", "meta", "axiom", "dawn", "aurora", "glitch"];
const suffix = Math.floor(Math.random() * 1e3).toString().padStart(3, "0");
return base[Math.floor(Math.random() * base.length)] + suffix;
}
function confirmSummary(info) {
log2(`
Creating persona:`);
log2(`Alias : ${info.alias}`);
log2(`Public Key : ${info.wallet.address}`);
log2(`Behavior Model : ${info.behaviorModel}`);
log2(`Style : ${info.style}`);
log2(`Bio : ${info.bio || "\u041D\u0435 \u0443\u043A\u0430\u0437\u0430\u043D\u043E"}`);
log2(`Tags : ${info.tags.length ? info.tags.join(", ") : "\u041D\u0435 \u0443\u043A\u0430\u0437\u0430\u043D\u043E"}`);
log2(`Avatar Hash : ${info.avatarHash || "\u041D\u0435 \u0443\u043A\u0430\u0437\u0430\u043D\u043E"}`);
}
function intro() {
log2(`
Welcome to persona init
\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
This wizard will guide you through creating a new persona.
A persona consists of:
\u2022 Alias \u2192 your display name
\u2022 Behavior Model \u2192 your thinking style
\u2022 Public Key \u2192 used to verify your thoughts
All data is stored locally in:
./storage/personas.json
\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
`);
}
function log2(msg) {
console.log(msg);
}
function logError(msg) {
console.error("\u274C", msg);
}
// src/cli/profile.ts
var import_fs8 = __toESM(require("fs"));
var import_path7 = __toESM(require("path"));
var thoughtsPath = import_path7.default.resolve("storage", "thoughts.json");
function profileCommand() {
const asJson = process.argv.includes("--json");
const activeAlias = getActivePersonaAlias();
if (!activeAlias) {
console.error("\u274C No active persona. Use: persona use <alias>");
return;
}
const personas = loadPersonas({ onlyValid: false });
const persona = personas.find((p) => p.alias === activeAlias);
if (!persona) {
console.error(`\u274C Active persona "${activeAlias}" not found.`);
return;
}
const {
alias,
publicKey,
behaviorModel,
createdAt,
style,
bio,
tags,
avatarHash,
signature
} = persona;
const isValid = verifyFullPersona(persona);
const createdAtHuman = createdAt ? new Date(createdAt).toLocaleString(void 0, {
year: "numeric",
month: "short",
day: "2-digit",
hour: "2-digit",
minute: "2-digit"
}) : "Unknown";
const thoughts = import_fs8.default.existsSync(thoughtsPath) ? JSON.parse(import_fs8.default.readFileSync(thoughtsPath, "utf-8")) : [];
const authoredThoughts = thoughts.filter((t) => t.author === alias);
const lastThought = authoredThoughts.at(-1);
if (asJson) {
console.log(
JSON.stringify(
{
alias,
publicKey,
behaviorModel,
createdAt,
createdAtHuman,
style,
bio,
tags,
avatarHash,
signature,
validSignature: isValid,
totalThoughts: authoredThoughts.length,
lastThought: lastThought?.content || null
},
null,
2
)
);
return;
}
console.log(`
Persona Profile
\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
Alias : ${alias}
Public Key : ${publicKey}
Behavior Model : ${behaviorModel}
Style : ${style || "\u041D\u0435 \u0443\u043A\u0430\u0437\u0430\u043D\u043E"}
Bio : ${bio || "\u041D\u0435 \u0443\u043A\u0430\u0437\u0430\u043D\u043E"}
Tags : ${tags?.length ? tags.join(", ") : "\u041D\u0435 \u0443\u043A\u0430\u0437\u0430\u043D\u043E"}
Avatar Hash : ${avatarHash || "\u041D\u0435 \u0443\u043A\u0430\u0437\u0430\u043D\u043E"}
Signature : ${signature ? "\u2705" : "\u274C"}
Valid Signature : ${isValid ? "\u2705 Yes" : "\u274C No"}
Created At : ${createdAtHuman}
Total Thoughts : ${authoredThoughts.length}
Last Thought : ${lastThought ? truncate(lastThought.content) : "\u2014"}
\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
`);
}
function truncate(text, max = 60) {
return text.length > max ? text.slice(0, max - 3) + "..." : text;
}
// src/cli/edit.ts
var import_fs9 = __toESM(require("fs"));
var import_path8 = __toESM(require("path"));
var import_readline3 = __toESM(require("readline"));
var personasPath = import_path8.default.resolve("storage", "personas.json");
var BEHAVIOR_MODELS3 = [
"socratic-punk",
"cypherpunk-minimalist",
"zen-daoist",
"rational-skeptic",
"dreamer-optimist"
];
async function editCommand() {
const oldAlias = process.argv[3];
const newAliasFlag = process.argv.find((arg) => arg.startsWith("--alias="));
const newBehaviorFlag = process.argv.find((arg) => arg.startsWith("--behavior="));
if (!oldAlias) {
console.error("\u274C Please specify an existing alias to edit.");
return;
}
if (!import_fs9.default.existsSync(personasPath)) {
console.error("\u274C No personas found.");
return;
}
const personas = JSON.parse(import_fs9.default.readFileSync(personasPath, "utf-8"));
const index = personas.findIndex((p) => p.alias === oldAlias);
if (index === -1) {
console.error(`\u274C Persona '${oldAlias}' not found.`);
return;
}
const newAlias = newAliasFlag?.split("=")[1] || await ask3("Enter new alias (leave blank to keep): ");
const newBehavior = newBehaviorFlag?.split("=")[1] || await selectBehavior(personas[index].behaviorModel);
if (newAlias && newAlias !== oldAlias && personas.some((p) => p.alias === newAlias)) {
console.error(`\u274C Alias '${newAlias}' is already taken.`);
return;
}
personas[index].alias = newAlias || personas[index].alias;
personas[index].behaviorModel = newBehavior || personas[index].behaviorModel;
import_fs9.default.writeFileSync(personasPath, JSON.stringify(personas, null, 2));
console.log(`\u2705 Updated persona '${oldAlias}' \u2192 '${personas[index].alias}'`);
}
function ask3(question) {
const rl = import_readline3.default.createInterface({ input: process.stdin, output: process.stdout });
return new Promise(
(resolve) => rl.question(question, (answer) => {
rl.close();
resolve(answer);
})
);
}
async function selectBehavior(current) {
console.log("\nAvailable behavior models:");
BEHAVIOR_MODELS3.forEach((model, i) => {
const marker = model === current ? "(current)" : "";
console.log(` ${i + 1}. ${model} ${marker}`);
});
const selected = await ask3("Select new behavior model [1\u20135] (leave blank to keep): ");
const index = parseInt(selected);
if (!selected.trim()) return current;
if (!isNaN(index) && BEHAVIOR_MODELS3[index - 1]) {
return BEHAVIOR_MODELS3[index - 1];
}
return current;
}
// src/cli/use.ts
var import_fs10 = __toESM(require("fs"));
var import_path9 = __toESM(require("path"));
var configPath = import_path9.default.resolve(".persona-config.json");
function useCommand() {
const alias = process.argv[3];
if (!alias) {
console.error("\u274C Please provide a persona alias. Example: persona use satoshi");
return;
}
const personas = loadPersonas();
const persona = personas.find((p) => p.alias === alias);
if (!persona) {
console.error(`\u274C No persona found with alias "${alias}"`);
return;
}
import_fs10.default.writeFileSync(configPath, JSON.stringify({ activePersona: alias }, null, 2));
console.log(`\u2705 Now using persona: ${alias}`);
}
// package.json
var package_default = {
name: "proof-of-persona",
version: "1.0.1",
description: "Cryptographically verified digital personalities with online reputations and LLM-style thinking.",
main: "dist/index.js",
type: "commonjs",
author: "badasya",
license: "MIT",
repository: {
type: "git",
url: "https://github.com/0xbadasya/proof-of-persona"
},
bugs: {
url: "https://github.com/0xbadasya/proof-of-persona/issues"
},
files: [
"dist",
"bin",
"LICENSE",
"README.md"
],
scripts: {
start: "ts-node src/index.ts",
dev: "ts-node playground/create-persona.ts",
build: "tsup",
test: "jest"
},
keywords: [
"persona",
"onchain",
"identity",
"web3",
"cli",
"digital-identity",
"zkproof",
"thoughts",
"anonymous",
"pseudonymous",
"self-sovereign",
"decentralized",
"crypto-persona",
"programmable",
"verified-identity",
"reputation",
"philosophy",
"behavior-model",
"crypto-identity",
"blockchain-identity",
"web3-authorship",
"daos",
"onchain-reputation",
"signed-content",
"verified-mind",
"llm-identity",
"privacy"
],
directories: {
doc: "docs",
test: "tests"
},
jest: {
transform: {
"^.+\\.ts?$": "ts-jest"
},
testEnvironment: "node",
moduleFileExtensions: [
"ts",
"js"
],
testMatch: [
"**/tests/**/*.test.ts"
]
},
bin: {
persona: "./bin/persona.js"
},
dependencies: {
"adm-zip": "^0.5.16",
archiver: "^7.0.1",
"docsify-darklight-theme": "^3.2.0",
dotenv: "^16.5.0",
ethers: "^5.8.0",
"fast-levenshtein": "^3.0.0",
inquirer: "^12.5.2",
openai: "^4.94.0",
uuid: "^11.1.0"
},
devDependencies: {
"@types/jest": "^29.5.14",
"@types/node": "^22.14.1",
jest: "^29.7.0",
"ts-jest": "^29.3.2",
"ts-node": "^10.9.2",
tsup: "^8.4.0",
typescript: "^5.8.3"
}
};
// src/cli/whoami.ts
function whoamiCommand() {
const alias = getActivePersonaAlias();
if (!alias) {
console.log("\u274C No active persona set. Use 'persona use <alias>' to activate one.");
return;
}
const personas = loadPersonas({ onlyValid: false });
const persona = personas.find((p) => p.alias === alias);
if (!persona) {
console.log(`\u274C No persona found for alias: ${alias}`);
return;
}
const { publicKey, behaviorModel, createdAt, style, signature, revoked } = persona;
const isValid = verifyFullPersona(persona);
console.log(`
Active Persona
\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
Alias : ${alias}
Public Key : ${publicKey}
Behavior Model : ${behaviorModel}
Style : ${style || "\u041D\u0435 \u0443\u043A\u0430\u0437\u0430\u043D\u043E"}
Signature : ${signature ? "\u2705" : "\u274C"}
Valid Signature : ${isValid ? "\u2705 Yes" : "\u274C No"}
Created At : ${new Date(createdAt).toLocaleString()}
Revoked : ${revoked}
`);
}
// src/cli/verify-persona.ts
function verifyPersonaCommand(alias) {
const personas = loadPersonas({ onlyValid: false });
const persona = personas.find((p) => p.alias === alias);
if (!persona) {
console.error(`\u274C Persona "${alias}" not found.`);
return;
}
const isValid = verifyFullPersona(persona);
console.log(`\u{1F9FE} Persona: ${persona.alias}`);
console.log(`\u{1F510} Signature valid: ${isValid ? "\u2705 Yes" : "\u274C No"}`);
}
// src/cli/revoke.ts
var import_fs11 = __toESM(require("fs"));
var import_path10 = __toESM(require("path"));
var personasPath2 = import_path10.default.resolve("storage", "personas.json");
var configPath2 = import_path10.default.resolve(".persona-config.json");
function revokeCommand() {
const alias = process.argv[3];
if (!alias) {
console.error("\u274C Please provide a persona alias to deactivate.");
return;
}
const personas = loadPersonas({ onlyValid: false });
const index = personas.findIndex((p) => p.alias === alias);
if (index === -1) {
console.error(`\u274C Persona "${alias}" not found.`);
return;
}
personas[index].revoked = true;
import_fs11.default.writeFileSync(personasPath2, JSON.stringify(personas, null, 2), "utf-8");
console.log(`\u26A0\uFE0F Persona "${alias}" has been deactivated.`);
const active = getActivePersonaAlias();
if (active === alias && import_fs11.default.existsSync(configPath2)) {
import_fs11.default.unlinkSync(configPath2);
console.log("\u26A0\uFE0F That was your active persona. It has been unset.");
}
}
// src/cli/index.ts
var command = process.argv[2];
if (command === "-v" || command === "--version") {
console.log(`persona v${package_default.version}`);
process.exit(0);
}
if (!command || command === "help" || command === "-h" || command === "--help") {
const sub = process.argv[3];
if (sub === "memory") {
console.log(`
\u{1F4D8} Memory commands:
persona memory log "..." Add an entry to memory
persona memory show Show memory log
persona memory clear Clear all memory
`);
process.exit(0);
}
printHelp();
process.exit(0);
}
switch (command) {
case "create":
createCommand();
break;
case "post":
postCommand();
break;
case "verify":
verifyCommand();
break;
case "verify-persona":
verifyPersonaCommand;
break;
case "list":
listCommand();
break;
case "clean":
cleanCommand();
break;
case "export":
exportCommand();
break;
case "memory":
memoryCommand();
break;
case "import":
importCommand();
break;
case "init":
initCommand();
break;
case "profile":
profileCommand();
break;
case "edit":
editCommand();
break;
case "use":
useCommand();
break;
case "whoami":
whoamiCommand();
break;
case "revoke":
revokeCommand();
break;
default:
console.log(`\u274C Unknown command: "${command}"`);
printHelp();
process.exit(1);
}
function printHelp() {
console.log(`
\u{1F9E0} persona CLI \u2013 your programmable onchain mind
by badasya \xB7 Badasya Software
\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
\u{1F4D8} General Commands:
create [alias] Create a new persona (optionally pass alias)
init Interactive persona creation wizard
edit <alias> [opts] Edit an existing persona (alias, style, etc.)
use <alias> Set the active persona
whoami Show current active persona
profile Show full profile of active persona
list [--all] List all personas (use --all to include revoked)
revoke <alias> \u{1F6AB} Revoke (deactivate) a persona
verify-persona <alias> \u2705 Verify signature of a persona
\u{1F9E0} Thought Commands:
post "text" [--topic=...] Post a new thought
verify Verify the last posted thought
clean Remove invalid or unsigned thoughts
memory log "..." \u270D\uFE0F Add an entry to your memory
memory show Show memory history
memory clear Clear all memory entries
\u{1F9F3} Import / Export:
export [type] Export thoughts or personas
Options:
--zip Export as .zip archive
--format=txt Export as .txt (default: JSON)
--only-active Only export current persona
import <file> Import data from .json file
Options:
--replace Overwrite existing data
\u{1F30D} Global Options:
-v, --version Show CLI version
-h, --help Show this help message
\u{1F4C1} Config File:
.persona-config.json Updated automatically via \`persona use <alias>\`
\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
\u{1F4CC} Examples:
persona create satoshi
persona edit satoshi --alias=cypher --style=chaotic
persona post "Crypto is freedom" --topic=cypherpunk
persona profile --json
persona verify
persona export thoughts --zip --only-active
persona import backup.json --replace
persona memory log "Posted my first thought"
\u{1F517} GitHub: https://github.com/0xbadasya/proof-of-persona
`);
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
printHelp
});