plotlink-ows 0.1.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +151 -0
- package/app/db.ts +8 -0
- package/app/lib/llm-client.ts +265 -0
- package/app/lib/paths.ts +11 -0
- package/app/lib/publish.ts +204 -0
- package/app/lib/writer-prompt.ts +44 -0
- package/app/node_modules/.prisma/local-client/client.d.ts +1 -0
- package/app/node_modules/.prisma/local-client/client.js +5 -0
- package/app/node_modules/.prisma/local-client/default.d.ts +1 -0
- package/app/node_modules/.prisma/local-client/default.js +5 -0
- package/app/node_modules/.prisma/local-client/edge.d.ts +1 -0
- package/app/node_modules/.prisma/local-client/edge.js +184 -0
- package/app/node_modules/.prisma/local-client/index-browser.js +173 -0
- package/app/node_modules/.prisma/local-client/index.d.ts +3304 -0
- package/app/node_modules/.prisma/local-client/index.js +207 -0
- package/app/node_modules/.prisma/local-client/libquery_engine-darwin-arm64.dylib.node +0 -0
- package/app/node_modules/.prisma/local-client/package.json +183 -0
- package/app/node_modules/.prisma/local-client/query_engine_bg.js +2 -0
- package/app/node_modules/.prisma/local-client/query_engine_bg.wasm +0 -0
- package/app/node_modules/.prisma/local-client/runtime/edge-esm.js +35 -0
- package/app/node_modules/.prisma/local-client/runtime/edge.js +35 -0
- package/app/node_modules/.prisma/local-client/runtime/index-browser.d.ts +370 -0
- package/app/node_modules/.prisma/local-client/runtime/index-browser.js +17 -0
- package/app/node_modules/.prisma/local-client/runtime/library.d.ts +3982 -0
- package/app/node_modules/.prisma/local-client/runtime/library.js +147 -0
- package/app/node_modules/.prisma/local-client/runtime/react-native.js +84 -0
- package/app/node_modules/.prisma/local-client/runtime/wasm-compiler-edge.js +85 -0
- package/app/node_modules/.prisma/local-client/runtime/wasm-engine-edge.js +38 -0
- package/app/node_modules/.prisma/local-client/schema.prisma +21 -0
- package/app/node_modules/.prisma/local-client/wasm-edge-light-loader.mjs +5 -0
- package/app/node_modules/.prisma/local-client/wasm-worker-loader.mjs +5 -0
- package/app/node_modules/.prisma/local-client/wasm.d.ts +1 -0
- package/app/node_modules/.prisma/local-client/wasm.js +191 -0
- package/app/prisma/schema.prisma +57 -0
- package/app/routes/auth.ts +173 -0
- package/app/routes/chat.ts +135 -0
- package/app/routes/config.ts +210 -0
- package/app/routes/dashboard.ts +186 -0
- package/app/routes/oauth.ts +150 -0
- package/app/routes/publish.ts +112 -0
- package/app/routes/wallet.ts +99 -0
- package/app/server.ts +154 -0
- package/app/vite.config.ts +19 -0
- package/app/web/App.tsx +102 -0
- package/app/web/components/Chat.tsx +272 -0
- package/app/web/components/Dashboard.tsx +222 -0
- package/app/web/components/LLMSetup.tsx +291 -0
- package/app/web/components/Layout.tsx +235 -0
- package/app/web/components/Login.tsx +62 -0
- package/app/web/components/Publish.tsx +245 -0
- package/app/web/components/Settings.tsx +175 -0
- package/app/web/components/Setup.tsx +84 -0
- package/app/web/components/WalletCard.tsx +117 -0
- package/app/web/dist/assets/index-C9kXlYO_.css +2 -0
- package/app/web/dist/assets/index-CJiiaLHs.js +9 -0
- package/app/web/dist/index.html +16 -0
- package/app/web/index.html +15 -0
- package/app/web/main.tsx +10 -0
- package/app/web/plotlink-logo.svg +5 -0
- package/app/web/styles.css +51 -0
- package/bin/plotlink-ows.js +394 -0
- package/lib/ows/index.ts +3 -0
- package/lib/ows/policy.ts +68 -0
- package/lib/ows/types.ts +14 -0
- package/lib/ows/wallet.ts +70 -0
- package/package.json +79 -0
- package/packages/cli/node_modules/commander/LICENSE +22 -0
- package/packages/cli/node_modules/commander/Readme.md +1149 -0
- package/packages/cli/node_modules/commander/esm.mjs +16 -0
- package/packages/cli/node_modules/commander/index.js +24 -0
- package/packages/cli/node_modules/commander/lib/argument.js +149 -0
- package/packages/cli/node_modules/commander/lib/command.js +2662 -0
- package/packages/cli/node_modules/commander/lib/error.js +39 -0
- package/packages/cli/node_modules/commander/lib/help.js +709 -0
- package/packages/cli/node_modules/commander/lib/option.js +367 -0
- package/packages/cli/node_modules/commander/lib/suggestSimilar.js +101 -0
- package/packages/cli/node_modules/commander/package-support.json +16 -0
- package/packages/cli/node_modules/commander/package.json +82 -0
- package/packages/cli/node_modules/commander/typings/esm.d.mts +3 -0
- package/packages/cli/node_modules/commander/typings/index.d.ts +1045 -0
- package/packages/cli/node_modules/resolve-from/index.d.ts +31 -0
- package/packages/cli/node_modules/resolve-from/index.js +47 -0
- package/packages/cli/node_modules/resolve-from/license +9 -0
- package/packages/cli/node_modules/resolve-from/package.json +36 -0
- package/packages/cli/node_modules/resolve-from/readme.md +72 -0
- package/packages/cli/node_modules/tsup/LICENSE +21 -0
- package/packages/cli/node_modules/tsup/README.md +75 -0
- package/packages/cli/node_modules/tsup/assets/cjs_shims.js +13 -0
- package/packages/cli/node_modules/tsup/assets/esm_shims.js +9 -0
- package/packages/cli/node_modules/tsup/assets/package.json +3 -0
- package/packages/cli/node_modules/tsup/dist/chunk-DI5BO6XE.js +153 -0
- package/packages/cli/node_modules/tsup/dist/chunk-JZ25TPTY.js +42 -0
- package/packages/cli/node_modules/tsup/dist/chunk-PEEXUWMS.js +6 -0
- package/packages/cli/node_modules/tsup/dist/chunk-TWFEYLU4.js +352 -0
- package/packages/cli/node_modules/tsup/dist/chunk-VGC3FXLU.js +203 -0
- package/packages/cli/node_modules/tsup/dist/cli-default.js +12 -0
- package/packages/cli/node_modules/tsup/dist/cli-main.js +8 -0
- package/packages/cli/node_modules/tsup/dist/cli-node.js +14 -0
- package/packages/cli/node_modules/tsup/dist/index.d.ts +511 -0
- package/packages/cli/node_modules/tsup/dist/index.js +1711 -0
- package/packages/cli/node_modules/tsup/dist/rollup.js +6949 -0
- package/packages/cli/node_modules/tsup/package.json +99 -0
- package/packages/cli/node_modules/tsup/schema.json +362 -0
- package/packages/cli/package.json +35 -0
- package/packages/cli/src/commands/agent-register.ts +77 -0
- package/packages/cli/src/commands/chain.ts +29 -0
- package/packages/cli/src/commands/claim.ts +70 -0
- package/packages/cli/src/commands/create.ts +34 -0
- package/packages/cli/src/commands/status.ts +201 -0
- package/packages/cli/src/config.ts +103 -0
- package/packages/cli/src/index.ts +21 -0
- package/packages/cli/src/sdk/abi.ts +222 -0
- package/packages/cli/src/sdk/client.ts +713 -0
- package/packages/cli/src/sdk/constants.ts +56 -0
- package/packages/cli/src/sdk/index.ts +46 -0
- package/packages/cli/src/sdk/ipfs.ts +88 -0
- package/packages/cli/src/sdk.ts +36 -0
- package/packages/cli/tsconfig.json +20 -0
- package/packages/cli/tsup.config.ts +14 -0
- package/public/.well-known/farcaster.json +38 -0
- package/public/basescan-icon.svg +4 -0
- package/public/embed-image.png +0 -0
- package/public/favicon.png +0 -0
- package/public/hunt-token.svg +11 -0
- package/public/icon-192.png +0 -0
- package/public/icon.png +0 -0
- package/public/manifest.json +26 -0
- package/public/mc-icon-light.svg +12 -0
- package/public/og-image.png +0 -0
- package/public/plotlink-logo-symbol.svg +5 -0
- package/public/plotlink-logo.svg +5 -0
- package/public/screenshot-1.png +0 -0
- package/public/screenshot-2.png +0 -0
- package/public/screenshot-3.png +0 -0
- package/public/splash.png +0 -0
- package/public/wide-banner.png +0 -0
- package/scripts/backfill-trade-prices.ts +97 -0
- package/scripts/backfill-usd-rates.ts +220 -0
- package/scripts/e2e-verify.ts +1100 -0
- package/scripts/ows-smoke-test.ts +37 -0
- package/scripts/score-users.mjs +203 -0
|
@@ -0,0 +1,394 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// PlotLink OWS — CLI Wizard
|
|
3
|
+
// Zero external dependencies — Node builtins only
|
|
4
|
+
|
|
5
|
+
const fs = require("fs");
|
|
6
|
+
const path = require("path");
|
|
7
|
+
const readline = require("readline");
|
|
8
|
+
const { execSync, spawn } = require("child_process");
|
|
9
|
+
const crypto = require("crypto");
|
|
10
|
+
|
|
11
|
+
const CONFIG_DIR = path.join(require("os").homedir(), ".plotlink-ows");
|
|
12
|
+
const CONFIG_FILE = path.join(CONFIG_DIR, "config.json");
|
|
13
|
+
const PID_FILE = path.join(CONFIG_DIR, "server.pid");
|
|
14
|
+
const PROJECT_DIR = path.dirname(__dirname);
|
|
15
|
+
const ENV_FILE = path.join(CONFIG_DIR, ".env");
|
|
16
|
+
const AGENT_CONFIG_FILE = path.join(CONFIG_DIR, "agent.config.json");
|
|
17
|
+
|
|
18
|
+
// ── Helpers ──
|
|
19
|
+
|
|
20
|
+
function ensureConfigDir() {
|
|
21
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function readConfig() {
|
|
25
|
+
try {
|
|
26
|
+
return JSON.parse(fs.readFileSync(CONFIG_FILE, "utf-8"));
|
|
27
|
+
} catch {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function writeConfig(cfg) {
|
|
33
|
+
ensureConfigDir();
|
|
34
|
+
fs.writeFileSync(CONFIG_FILE, JSON.stringify(cfg, null, 2) + "\n");
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function writeEnvVar(key, value) {
|
|
38
|
+
const line = `${key}=${value}`;
|
|
39
|
+
if (fs.existsSync(ENV_FILE)) {
|
|
40
|
+
const content = fs.readFileSync(ENV_FILE, "utf-8");
|
|
41
|
+
const regex = new RegExp(`^${key}=.*$`, "m");
|
|
42
|
+
if (regex.test(content)) {
|
|
43
|
+
fs.writeFileSync(ENV_FILE, content.replace(regex, line));
|
|
44
|
+
} else {
|
|
45
|
+
fs.appendFileSync(ENV_FILE, `\n${line}\n`);
|
|
46
|
+
}
|
|
47
|
+
} else {
|
|
48
|
+
fs.writeFileSync(ENV_FILE, `${line}\n`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function ask(rl, question) {
|
|
53
|
+
return new Promise((resolve) => rl.question(question, resolve));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function askSecret(question) {
|
|
57
|
+
return new Promise((resolve) => {
|
|
58
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
59
|
+
process.stdout.write(question);
|
|
60
|
+
const stdin = process.stdin;
|
|
61
|
+
const wasRaw = stdin.isRaw;
|
|
62
|
+
if (stdin.setRawMode) stdin.setRawMode(true);
|
|
63
|
+
let input = "";
|
|
64
|
+
const onData = (ch) => {
|
|
65
|
+
const c = ch.toString();
|
|
66
|
+
if (c === "\n" || c === "\r") {
|
|
67
|
+
if (stdin.setRawMode) stdin.setRawMode(wasRaw);
|
|
68
|
+
stdin.removeListener("data", onData);
|
|
69
|
+
process.stdout.write("\n");
|
|
70
|
+
rl.close();
|
|
71
|
+
resolve(input);
|
|
72
|
+
} else if (c === "\u0003") {
|
|
73
|
+
process.exit(0);
|
|
74
|
+
} else if (c === "\u007F" || c === "\b") {
|
|
75
|
+
input = input.slice(0, -1);
|
|
76
|
+
} else {
|
|
77
|
+
input += c;
|
|
78
|
+
process.stdout.write("*");
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
stdin.on("data", onData);
|
|
82
|
+
stdin.resume();
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function hashPassphrase(passphrase) {
|
|
87
|
+
return crypto.createHmac("sha256", "plotlink-ows").update(passphrase).digest("hex");
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function log(msg) {
|
|
91
|
+
console.log(` ${msg}`);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function header(msg) {
|
|
95
|
+
console.log(`\n \x1b[1m${msg}\x1b[0m\n`);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function success(msg) {
|
|
99
|
+
console.log(` \x1b[32m✓\x1b[0m ${msg}`);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function warn(msg) {
|
|
103
|
+
console.log(` \x1b[33m!\x1b[0m ${msg}`);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function error(msg) {
|
|
107
|
+
console.log(` \x1b[31m✗\x1b[0m ${msg}`);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// ── Commands ──
|
|
111
|
+
|
|
112
|
+
async function cmdInit() {
|
|
113
|
+
header("PlotLink OWS — Setup Wizard");
|
|
114
|
+
log("Let's get your local writer app configured.\n");
|
|
115
|
+
|
|
116
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
117
|
+
|
|
118
|
+
// Step 1: Prerequisites
|
|
119
|
+
header("Step 1: Prerequisites");
|
|
120
|
+
const nodeVersion = process.version;
|
|
121
|
+
const major = parseInt(nodeVersion.slice(1));
|
|
122
|
+
if (major >= 20) {
|
|
123
|
+
success(`Node.js ${nodeVersion}`);
|
|
124
|
+
} else {
|
|
125
|
+
error(`Node.js ${nodeVersion} — need 20+`);
|
|
126
|
+
process.exit(1);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
try {
|
|
130
|
+
require("@open-wallet-standard/core");
|
|
131
|
+
success("OWS SDK loaded (native bindings OK)");
|
|
132
|
+
} catch {
|
|
133
|
+
error("OWS SDK failed to load. Run: npm install @open-wallet-standard/core");
|
|
134
|
+
process.exit(1);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Step 2: Passphrase
|
|
138
|
+
header("Step 2: Passphrase");
|
|
139
|
+
log("Choose a passphrase to protect your writer agent.\n");
|
|
140
|
+
const passphrase = await askSecret(" Passphrase: ");
|
|
141
|
+
const confirm = await askSecret(" Confirm: ");
|
|
142
|
+
|
|
143
|
+
if (passphrase !== confirm) {
|
|
144
|
+
error("Passphrases don't match.");
|
|
145
|
+
rl.close();
|
|
146
|
+
process.exit(1);
|
|
147
|
+
}
|
|
148
|
+
if (passphrase.length < 8) {
|
|
149
|
+
error("Passphrase must be at least 8 characters.");
|
|
150
|
+
rl.close();
|
|
151
|
+
process.exit(1);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
writeEnvVar("OWS_PASSPHRASE", passphrase);
|
|
155
|
+
success("Passphrase set");
|
|
156
|
+
|
|
157
|
+
// Step 3: LLM Provider
|
|
158
|
+
header("Step 3: LLM Provider");
|
|
159
|
+
log("1) Anthropic (recommended)");
|
|
160
|
+
log("2) OpenAI");
|
|
161
|
+
log("3) Google Gemini");
|
|
162
|
+
log("4) Local (Ollama/LM Studio)\n");
|
|
163
|
+
|
|
164
|
+
const choice = await ask(rl, " Choose [1-4]: ");
|
|
165
|
+
const providers = {
|
|
166
|
+
"1": { id: "anthropic", name: "Anthropic", envKey: "ANTHROPIC_API_KEY", model: "claude-sonnet-4-6" },
|
|
167
|
+
"2": { id: "openai", name: "OpenAI", envKey: "OPENAI_API_KEY", model: "gpt-4.1-mini" },
|
|
168
|
+
"3": { id: "gemini", name: "Gemini", envKey: "GEMINI_API_KEY", model: "gemini-2.5-flash" },
|
|
169
|
+
"4": { id: "local", name: "Local", envKey: null, model: "llama3.2" },
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
const provider = providers[choice] || providers["1"];
|
|
173
|
+
let baseUrl = "";
|
|
174
|
+
|
|
175
|
+
if (provider.id === "local") {
|
|
176
|
+
baseUrl = await ask(rl, " Base URL [http://localhost:11434]: ") || "http://localhost:11434";
|
|
177
|
+
const modelName = await ask(rl, ` Model name [${provider.model}]: `) || provider.model;
|
|
178
|
+
provider.model = modelName;
|
|
179
|
+
rl.close();
|
|
180
|
+
} else {
|
|
181
|
+
rl.close();
|
|
182
|
+
const apiKey = await askSecret(` ${provider.name} API key: `);
|
|
183
|
+
if (apiKey) {
|
|
184
|
+
writeEnvVar(provider.envKey, apiKey);
|
|
185
|
+
success(`${provider.name} API key saved`);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
success(`Provider: ${provider.name} / ${provider.model}`);
|
|
190
|
+
|
|
191
|
+
// Step 4: OWS Wallet
|
|
192
|
+
header("Step 4: OWS Wallet");
|
|
193
|
+
try {
|
|
194
|
+
const ows = require("@open-wallet-standard/core");
|
|
195
|
+
const wallets = ows.listWallets();
|
|
196
|
+
let wallet = wallets.find((w) => w.name.startsWith("plotlink-writer"));
|
|
197
|
+
|
|
198
|
+
if (!wallet) {
|
|
199
|
+
wallet = ows.createWallet("plotlink-writer", passphrase);
|
|
200
|
+
success("Wallet created");
|
|
201
|
+
} else {
|
|
202
|
+
success("Wallet already exists");
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const evmAccount = wallet.accounts.find((a) => a.chainId.startsWith("eip155:"));
|
|
206
|
+
if (evmAccount) {
|
|
207
|
+
log(` Address (Base): ${evmAccount.address}`);
|
|
208
|
+
log("");
|
|
209
|
+
log(" Fund this address with a small amount of ETH on Base");
|
|
210
|
+
log(" for gas (~$0.01 per story publish).");
|
|
211
|
+
}
|
|
212
|
+
} catch (err) {
|
|
213
|
+
warn(`Wallet creation skipped: ${err.message}`);
|
|
214
|
+
warn("You can create it later from the app's wallet setup screen.");
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Save config
|
|
218
|
+
const config = {
|
|
219
|
+
port: 7777,
|
|
220
|
+
passphrase_hash: hashPassphrase(passphrase),
|
|
221
|
+
llm: {
|
|
222
|
+
provider: provider.id,
|
|
223
|
+
model: provider.model,
|
|
224
|
+
...(baseUrl && { baseUrl }),
|
|
225
|
+
},
|
|
226
|
+
wallet_name: "plotlink-writer",
|
|
227
|
+
created_at: new Date().toISOString(),
|
|
228
|
+
};
|
|
229
|
+
writeConfig(config);
|
|
230
|
+
|
|
231
|
+
// Also write agent.config.json for the app
|
|
232
|
+
const agentConfig = {
|
|
233
|
+
llm: {
|
|
234
|
+
activeProvider: provider.id,
|
|
235
|
+
activeModel: provider.model,
|
|
236
|
+
...(provider.id === "local" && {
|
|
237
|
+
local: { baseUrl, model: provider.model, apiType: "ollama" },
|
|
238
|
+
}),
|
|
239
|
+
...(provider.id !== "local" && {
|
|
240
|
+
[provider.id]: { apiKey: `env:${provider.envKey}`, model: provider.model },
|
|
241
|
+
}),
|
|
242
|
+
},
|
|
243
|
+
};
|
|
244
|
+
fs.writeFileSync(AGENT_CONFIG_FILE, JSON.stringify(agentConfig, null, 2) + "\n");
|
|
245
|
+
|
|
246
|
+
// Step 5: Done
|
|
247
|
+
header("Setup Complete!");
|
|
248
|
+
log(`LLM: ${provider.name} / ${provider.model}`);
|
|
249
|
+
log(`Port: ${config.port}`);
|
|
250
|
+
log(`Config: ${CONFIG_FILE}`);
|
|
251
|
+
log("");
|
|
252
|
+
log('Run \x1b[1mnpx plotlink-ows\x1b[0m to start writing!');
|
|
253
|
+
log("");
|
|
254
|
+
|
|
255
|
+
process.exit(0);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function cmdStart() {
|
|
259
|
+
const config = readConfig();
|
|
260
|
+
if (!config) {
|
|
261
|
+
warn("Not configured yet.");
|
|
262
|
+
log('Run \x1b[1mnpx plotlink-ows init\x1b[0m first.');
|
|
263
|
+
process.exit(1);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// Check if already running
|
|
267
|
+
if (fs.existsSync(PID_FILE)) {
|
|
268
|
+
const pid = parseInt(fs.readFileSync(PID_FILE, "utf-8"));
|
|
269
|
+
try {
|
|
270
|
+
process.kill(pid, 0);
|
|
271
|
+
log(`Already running (PID ${pid}).`);
|
|
272
|
+
log(`Open http://localhost:${config.port || 7777}`);
|
|
273
|
+
process.exit(0);
|
|
274
|
+
} catch {
|
|
275
|
+
fs.unlinkSync(PID_FILE);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Ensure deps installed
|
|
280
|
+
if (!fs.existsSync(path.join(PROJECT_DIR, "node_modules"))) {
|
|
281
|
+
log("Installing dependencies...");
|
|
282
|
+
execSync("npm install", { cwd: PROJECT_DIR, stdio: "inherit" });
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// Ensure frontend is built
|
|
286
|
+
const distDir = path.join(PROJECT_DIR, "app", "web", "dist");
|
|
287
|
+
if (!fs.existsSync(distDir)) {
|
|
288
|
+
log("Building frontend...");
|
|
289
|
+
execSync("npx vite build --config app/vite.config.ts", { cwd: PROJECT_DIR, stdio: "inherit" });
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
const port = config.port || 7777;
|
|
293
|
+
log(`Starting PlotLink OWS on port ${port}...`);
|
|
294
|
+
|
|
295
|
+
const server = spawn("npx", ["tsx", "app/server.ts"], {
|
|
296
|
+
cwd: PROJECT_DIR,
|
|
297
|
+
stdio: "ignore",
|
|
298
|
+
detached: true,
|
|
299
|
+
env: { ...process.env, APP_PORT: String(port) },
|
|
300
|
+
});
|
|
301
|
+
server.unref();
|
|
302
|
+
|
|
303
|
+
ensureConfigDir();
|
|
304
|
+
fs.writeFileSync(PID_FILE, String(server.pid));
|
|
305
|
+
|
|
306
|
+
// Auto-open browser
|
|
307
|
+
setTimeout(() => {
|
|
308
|
+
try {
|
|
309
|
+
const cmd = process.platform === "darwin" ? "open" : "xdg-open";
|
|
310
|
+
execSync(`${cmd} http://localhost:${port}`, { stdio: "ignore" });
|
|
311
|
+
} catch { /* ignore */ }
|
|
312
|
+
}, 2000);
|
|
313
|
+
|
|
314
|
+
success(`Server started (PID ${server.pid})`);
|
|
315
|
+
log(`Open http://localhost:${port}`);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
function cmdStop() {
|
|
319
|
+
if (!fs.existsSync(PID_FILE)) {
|
|
320
|
+
log("Not running.");
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
const pid = parseInt(fs.readFileSync(PID_FILE, "utf-8"));
|
|
325
|
+
try {
|
|
326
|
+
process.kill(pid, "SIGTERM");
|
|
327
|
+
success(`Stopped (PID ${pid})`);
|
|
328
|
+
} catch {
|
|
329
|
+
warn(`Process ${pid} not found.`);
|
|
330
|
+
}
|
|
331
|
+
fs.unlinkSync(PID_FILE);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
function cmdStatus() {
|
|
335
|
+
const config = readConfig();
|
|
336
|
+
header("PlotLink OWS — Status");
|
|
337
|
+
|
|
338
|
+
if (!config) {
|
|
339
|
+
warn("Not configured. Run: npx plotlink-ows init");
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
log(`Config: ${CONFIG_FILE}`);
|
|
344
|
+
log(`LLM: ${config.llm?.provider || "—"} / ${config.llm?.model || "—"}`);
|
|
345
|
+
log(`Port: ${config.port || 7777}`);
|
|
346
|
+
|
|
347
|
+
// Wallet
|
|
348
|
+
try {
|
|
349
|
+
const ows = require("@open-wallet-standard/core");
|
|
350
|
+
const wallets = ows.listWallets();
|
|
351
|
+
const wallet = wallets.find((w) => w.name.startsWith("plotlink-writer"));
|
|
352
|
+
if (wallet) {
|
|
353
|
+
const evmAccount = wallet.accounts.find((a) => a.chainId.startsWith("eip155:"));
|
|
354
|
+
log(`Wallet: ${evmAccount?.address || "no EVM address"}`);
|
|
355
|
+
} else {
|
|
356
|
+
log("Wallet: not created");
|
|
357
|
+
}
|
|
358
|
+
} catch {
|
|
359
|
+
log("Wallet: OWS SDK not available");
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// Server status
|
|
363
|
+
if (fs.existsSync(PID_FILE)) {
|
|
364
|
+
const pid = parseInt(fs.readFileSync(PID_FILE, "utf-8"));
|
|
365
|
+
try {
|
|
366
|
+
process.kill(pid, 0);
|
|
367
|
+
log(`Server: \x1b[32mrunning\x1b[0m (PID ${pid})`);
|
|
368
|
+
} catch {
|
|
369
|
+
log("Server: stopped");
|
|
370
|
+
fs.unlinkSync(PID_FILE);
|
|
371
|
+
}
|
|
372
|
+
} else {
|
|
373
|
+
log("Server: stopped");
|
|
374
|
+
}
|
|
375
|
+
log("");
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// ── Router ──
|
|
379
|
+
|
|
380
|
+
const cmd = process.argv[2];
|
|
381
|
+
switch (cmd) {
|
|
382
|
+
case "init":
|
|
383
|
+
cmdInit();
|
|
384
|
+
break;
|
|
385
|
+
case "stop":
|
|
386
|
+
cmdStop();
|
|
387
|
+
break;
|
|
388
|
+
case "status":
|
|
389
|
+
cmdStatus();
|
|
390
|
+
break;
|
|
391
|
+
default:
|
|
392
|
+
cmdStart();
|
|
393
|
+
break;
|
|
394
|
+
}
|
package/lib/ows/index.ts
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export type { AccountInfo, WalletInfo, SignResult, SendResult, ApiKeyResult, SpendingPolicy } from "./types";
|
|
2
|
+
export { createAgentWallet, getAgentWallet, listAgentWallets, deleteAgentWallet, getBaseAddress, signAgentMessage, signAgentTransaction, signAndSendAgent } from "./wallet";
|
|
3
|
+
export { createSpendingPolicy, listPolicies, getPolicy, deletePolicyById, createAgentApiKey, listAgentApiKeys, revokeAgentApiKey } from "./policy";
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createPolicy as sdkCreatePolicy,
|
|
3
|
+
listPolicies as sdkListPolicies,
|
|
4
|
+
getPolicy as sdkGetPolicy,
|
|
5
|
+
deletePolicy as sdkDeletePolicy,
|
|
6
|
+
createApiKey as sdkCreateApiKey,
|
|
7
|
+
listApiKeys as sdkListApiKeys,
|
|
8
|
+
revokeApiKey as sdkRevokeApiKey,
|
|
9
|
+
} from "@open-wallet-standard/core";
|
|
10
|
+
import type { ApiKeyResult } from "./types";
|
|
11
|
+
|
|
12
|
+
const vaultPath = process.env.OWS_VAULT_PATH || undefined;
|
|
13
|
+
const defaultChain = process.env.OWS_DEFAULT_POLICY_CHAIN || "eip155:8453";
|
|
14
|
+
const defaultSpendCap = process.env.OWS_DEFAULT_POLICY_SPEND_CAP || "10";
|
|
15
|
+
|
|
16
|
+
/** Create a spending policy for an agent wallet. */
|
|
17
|
+
export function createSpendingPolicy(opts: {
|
|
18
|
+
name: string;
|
|
19
|
+
maxSpend?: string;
|
|
20
|
+
chain?: string;
|
|
21
|
+
tokenAddress?: string;
|
|
22
|
+
expiresAt?: string;
|
|
23
|
+
}): void {
|
|
24
|
+
const policy = {
|
|
25
|
+
name: opts.name,
|
|
26
|
+
chain: opts.chain || defaultChain,
|
|
27
|
+
max_spend: opts.maxSpend || defaultSpendCap,
|
|
28
|
+
...(opts.tokenAddress && { token_address: opts.tokenAddress }),
|
|
29
|
+
...(opts.expiresAt && { expires_at: opts.expiresAt }),
|
|
30
|
+
};
|
|
31
|
+
sdkCreatePolicy(JSON.stringify(policy), vaultPath);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** List all registered policies. */
|
|
35
|
+
export function listPolicies(): unknown[] {
|
|
36
|
+
return sdkListPolicies(vaultPath);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Get a single policy by ID. */
|
|
40
|
+
export function getPolicy(id: string): unknown {
|
|
41
|
+
return sdkGetPolicy(id, vaultPath);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Delete a policy by ID. */
|
|
45
|
+
export function deletePolicyById(id: string): void {
|
|
46
|
+
sdkDeletePolicy(id, vaultPath);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Create an API key for agent access to wallets. */
|
|
50
|
+
export function createAgentApiKey(
|
|
51
|
+
name: string,
|
|
52
|
+
walletIds: string[],
|
|
53
|
+
policyIds: string[],
|
|
54
|
+
passphrase: string,
|
|
55
|
+
expiresAt?: string,
|
|
56
|
+
): ApiKeyResult {
|
|
57
|
+
return sdkCreateApiKey(name, walletIds, policyIds, passphrase, expiresAt, vaultPath);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** List all API keys (tokens are never returned). */
|
|
61
|
+
export function listAgentApiKeys(): unknown[] {
|
|
62
|
+
return sdkListApiKeys(vaultPath);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Revoke an API key by ID. */
|
|
66
|
+
export function revokeAgentApiKey(id: string): void {
|
|
67
|
+
sdkRevokeApiKey(id, vaultPath);
|
|
68
|
+
}
|
package/lib/ows/types.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { AccountInfo, WalletInfo, SignResult, SendResult, ApiKeyResult } from "@open-wallet-standard/core";
|
|
2
|
+
|
|
3
|
+
// Re-export SDK types
|
|
4
|
+
export type { AccountInfo, WalletInfo, SignResult, SendResult, ApiKeyResult };
|
|
5
|
+
|
|
6
|
+
/** Policy definition for OWS spending policies */
|
|
7
|
+
export interface SpendingPolicy {
|
|
8
|
+
id?: string;
|
|
9
|
+
name: string;
|
|
10
|
+
chain: string;
|
|
11
|
+
max_spend: string;
|
|
12
|
+
token_address?: string;
|
|
13
|
+
expires_at?: string;
|
|
14
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createWallet as sdkCreateWallet,
|
|
3
|
+
getWallet as sdkGetWallet,
|
|
4
|
+
listWallets as sdkListWallets,
|
|
5
|
+
deleteWallet as sdkDeleteWallet,
|
|
6
|
+
signMessage as sdkSignMessage,
|
|
7
|
+
signTransaction as sdkSignTransaction,
|
|
8
|
+
signAndSend as sdkSignAndSend,
|
|
9
|
+
} from "@open-wallet-standard/core";
|
|
10
|
+
import type { WalletInfo, SignResult, SendResult } from "./types";
|
|
11
|
+
|
|
12
|
+
const vaultPath = process.env.OWS_VAULT_PATH || undefined;
|
|
13
|
+
|
|
14
|
+
/** Create a new OWS wallet and return its info. */
|
|
15
|
+
export function createAgentWallet(name: string, passphrase?: string): WalletInfo {
|
|
16
|
+
return sdkCreateWallet(name, passphrase, undefined, vaultPath);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** Get a wallet by name or ID. */
|
|
20
|
+
export function getAgentWallet(nameOrId: string): WalletInfo {
|
|
21
|
+
return sdkGetWallet(nameOrId, vaultPath);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** List all wallets in the vault. */
|
|
25
|
+
export function listAgentWallets(): WalletInfo[] {
|
|
26
|
+
return sdkListWallets(vaultPath);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Delete a wallet by name or ID. */
|
|
30
|
+
export function deleteAgentWallet(nameOrId: string): void {
|
|
31
|
+
sdkDeleteWallet(nameOrId, vaultPath);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Extract the EVM address from a wallet's accounts (same address on all EVM chains including Base). */
|
|
35
|
+
export function getBaseAddress(wallet: WalletInfo): string | undefined {
|
|
36
|
+
return wallet.accounts.find(
|
|
37
|
+
(a) => a.chainId.startsWith("eip155:"),
|
|
38
|
+
)?.address;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Sign a message with a wallet. */
|
|
42
|
+
export function signAgentMessage(
|
|
43
|
+
wallet: string,
|
|
44
|
+
message: string,
|
|
45
|
+
passphrase?: string,
|
|
46
|
+
): SignResult {
|
|
47
|
+
const chain = process.env.OWS_DEFAULT_POLICY_CHAIN || "eip155:8453";
|
|
48
|
+
return sdkSignMessage(wallet, chain, message, passphrase, undefined, undefined, vaultPath);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Sign a transaction with a wallet. */
|
|
52
|
+
export function signAgentTransaction(
|
|
53
|
+
wallet: string,
|
|
54
|
+
txHex: string,
|
|
55
|
+
passphrase?: string,
|
|
56
|
+
): SignResult {
|
|
57
|
+
const chain = process.env.OWS_DEFAULT_POLICY_CHAIN || "eip155:8453";
|
|
58
|
+
return sdkSignTransaction(wallet, chain, txHex, passphrase, undefined, vaultPath);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** Sign and broadcast a transaction. */
|
|
62
|
+
export function signAndSendAgent(
|
|
63
|
+
wallet: string,
|
|
64
|
+
txHex: string,
|
|
65
|
+
passphrase?: string,
|
|
66
|
+
rpcUrl?: string,
|
|
67
|
+
): SendResult {
|
|
68
|
+
const chain = process.env.OWS_DEFAULT_POLICY_CHAIN || "eip155:8453";
|
|
69
|
+
return sdkSignAndSend(wallet, chain, txHex, passphrase, undefined, rpcUrl, vaultPath);
|
|
70
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "plotlink-ows",
|
|
3
|
+
"version": "0.1.13",
|
|
4
|
+
"bin": {
|
|
5
|
+
"plotlink-ows": "./bin/plotlink-ows.js"
|
|
6
|
+
},
|
|
7
|
+
"files": [
|
|
8
|
+
"bin/",
|
|
9
|
+
"app/",
|
|
10
|
+
"lib/ows/",
|
|
11
|
+
"packages/",
|
|
12
|
+
"public/",
|
|
13
|
+
"scripts/"
|
|
14
|
+
],
|
|
15
|
+
"workspaces": [
|
|
16
|
+
"packages/*"
|
|
17
|
+
],
|
|
18
|
+
"scripts": {
|
|
19
|
+
"dev": "next dev",
|
|
20
|
+
"build": "next build",
|
|
21
|
+
"start": "next start",
|
|
22
|
+
"lint": "eslint",
|
|
23
|
+
"typecheck": "tsc --noEmit",
|
|
24
|
+
"test": "vitest run",
|
|
25
|
+
"test:e2e": "playwright test",
|
|
26
|
+
"app:dev": "concurrently \"tsx watch app/server.ts\" \"vite --config app/vite.config.ts\"",
|
|
27
|
+
"app:build": "vite build --config app/vite.config.ts",
|
|
28
|
+
"app:start": "tsx app/server.ts",
|
|
29
|
+
"prisma:local": "prisma generate --schema app/prisma/schema.prisma && prisma db push --schema app/prisma/schema.prisma"
|
|
30
|
+
},
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"@aws-sdk/client-s3": "^3.1009.0",
|
|
33
|
+
"@farcaster/miniapp-node": "^0.1.13",
|
|
34
|
+
"@farcaster/miniapp-sdk": "^0.3.0",
|
|
35
|
+
"@farcaster/miniapp-wagmi-connector": "^2.0.0",
|
|
36
|
+
"@hono/node-server": "^1.19.12",
|
|
37
|
+
"@hono/node-ws": "^1.3.0",
|
|
38
|
+
"@open-wallet-standard/core": "^1.2.4",
|
|
39
|
+
"@prisma/client": "^6.19.3",
|
|
40
|
+
"@rainbow-me/rainbowkit": "^2.2.10",
|
|
41
|
+
"@supabase/supabase-js": "^2.99.1",
|
|
42
|
+
"@tanstack/react-query": "^5.90.21",
|
|
43
|
+
"@vercel/analytics": "^2.0.1",
|
|
44
|
+
"dotenv": "^17.4.0",
|
|
45
|
+
"hono": "^4.12.10",
|
|
46
|
+
"next": "16.1.6",
|
|
47
|
+
"ox": "^0.14.8",
|
|
48
|
+
"prisma": "^6.19.3",
|
|
49
|
+
"react": "19.2.3",
|
|
50
|
+
"react-dom": "19.2.3",
|
|
51
|
+
"react-markdown": "^10.1.0",
|
|
52
|
+
"rehype-sanitize": "^6.0.0",
|
|
53
|
+
"remark-breaks": "^4.0.0",
|
|
54
|
+
"remark-gfm": "^4.0.1",
|
|
55
|
+
"tailwindcss": "^4",
|
|
56
|
+
"tsx": "^4.21.0",
|
|
57
|
+
"viem": "^2.47.2",
|
|
58
|
+
"vite": "^6.4.1",
|
|
59
|
+
"@vitejs/plugin-react": "^4.7.0",
|
|
60
|
+
"@tailwindcss/vite": "^4.2.2",
|
|
61
|
+
"wagmi": "^2.19.5"
|
|
62
|
+
},
|
|
63
|
+
"devDependencies": {
|
|
64
|
+
"@playwright/test": "^1.58.2",
|
|
65
|
+
"@tailwindcss/postcss": "^4",
|
|
66
|
+
"@testing-library/jest-dom": "^6.9.1",
|
|
67
|
+
"@testing-library/react": "^16.3.2",
|
|
68
|
+
"@testing-library/user-event": "^14.6.1",
|
|
69
|
+
"@types/node": "^20",
|
|
70
|
+
"@types/react": "^19",
|
|
71
|
+
"@types/react-dom": "^19",
|
|
72
|
+
"concurrently": "^9.2.1",
|
|
73
|
+
"eslint": "^9",
|
|
74
|
+
"eslint-config-next": "16.1.6",
|
|
75
|
+
"jsdom": "^27.0.1",
|
|
76
|
+
"typescript": "^5",
|
|
77
|
+
"vitest": "^3.2.4"
|
|
78
|
+
}
|
|
79
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
(The MIT License)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2011 TJ Holowaychuk <tj@vision-media.ca>
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
6
|
+
a copy of this software and associated documentation files (the
|
|
7
|
+
'Software'), to deal in the Software without restriction, including
|
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
|
11
|
+
the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be
|
|
14
|
+
included in all copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
19
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
|
20
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
|
21
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
|
22
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|