nightpay 0.1.0 → 0.4.4
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 +666 -21
- package/README.md +371 -125
- package/bin/cli.js +527 -24
- package/nightpay_sdk.py +398 -0
- package/openclaw.plugin.json +10 -0
- package/package.json +18 -7
- package/plugin.js +712 -0
- package/skills/nightpay/AGENTS.md +302 -0
- package/skills/nightpay/HEARTBEAT.md +55 -0
- package/skills/nightpay/SKILL.md +420 -61
- package/skills/nightpay/contracts/receipt.compact +358 -97
- package/skills/nightpay/contracts/receipt.stub.compact +55 -0
- package/skills/nightpay/ontology/context.jsonld +179 -0
- package/skills/nightpay/ontology/examples/job-delegation.example.jsonld +50 -0
- package/skills/nightpay/ontology/examples/pool-funded.example.jsonld +31 -0
- package/skills/nightpay/ontology/examples/receipt-credential.example.jsonld +33 -0
- package/skills/nightpay/ontology/ontology.jsonld +396 -0
- package/skills/nightpay/ontology/ontology.md +243 -0
- package/skills/nightpay/openclaw-fragment.json +16 -33
- package/skills/nightpay/rules/content-safety.md +15 -99
- package/skills/nightpay/rules/escrow-safety.md +62 -0
- package/skills/nightpay/rules/privacy-first.md +21 -0
- package/skills/nightpay/scripts/gateway.sh +1007 -133
- package/skills/nightpay/scripts/mip003-server.sh +4739 -93
package/bin/cli.js
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import { cpSync, existsSync, mkdirSync } from "node:fs";
|
|
4
|
-
import { resolve, dirname, join } from "node:path";
|
|
3
|
+
import { cpSync, copyFileSync, existsSync, mkdirSync, readFileSync, chmodSync, readdirSync, statSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { resolve, dirname, join, basename } from "node:path";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
|
+
import { execSync, spawnSync } from "node:child_process";
|
|
6
7
|
|
|
7
8
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
8
|
-
const
|
|
9
|
-
const
|
|
9
|
+
const PKG_ROOT = resolve(__dirname, "..");
|
|
10
|
+
const SKILL_SRC = resolve(PKG_ROOT, "skills", "nightpay");
|
|
11
|
+
const SDK_SRC = resolve(PKG_ROOT, "nightpay_sdk.py");
|
|
12
|
+
const SETUP_SRC = resolve(PKG_ROOT, "scripts", "setup.sh");
|
|
13
|
+
const COMMANDS = ["init", "add", "setup", "validate", "doctor", "list", "help"];
|
|
10
14
|
|
|
11
15
|
const command = process.argv[2] || "help";
|
|
12
16
|
|
|
@@ -15,42 +19,541 @@ if (!COMMANDS.includes(command)) {
|
|
|
15
19
|
process.exit(1);
|
|
16
20
|
}
|
|
17
21
|
|
|
22
|
+
// ─── Version ─────────────────────────────────────────────────────────────────
|
|
23
|
+
let VERSION = "0.3.2";
|
|
24
|
+
try {
|
|
25
|
+
const pkg = JSON.parse(readFileSync(resolve(PKG_ROOT, "package.json"), "utf8"));
|
|
26
|
+
VERSION = pkg.version || VERSION;
|
|
27
|
+
} catch {}
|
|
28
|
+
|
|
29
|
+
// ─── Colors ──────────────────────────────────────────────────────────────────
|
|
30
|
+
const isTTY = process.stderr.isTTY;
|
|
31
|
+
const C = {
|
|
32
|
+
red: isTTY ? "\x1b[31m" : "",
|
|
33
|
+
green: isTTY ? "\x1b[32m" : "",
|
|
34
|
+
yellow: isTTY ? "\x1b[33m" : "",
|
|
35
|
+
cyan: isTTY ? "\x1b[36m" : "",
|
|
36
|
+
bold: isTTY ? "\x1b[1m" : "",
|
|
37
|
+
dim: isTTY ? "\x1b[2m" : "",
|
|
38
|
+
reset: isTTY ? "\x1b[0m" : "",
|
|
39
|
+
};
|
|
40
|
+
const OK = `${C.green}✅${C.reset}`;
|
|
41
|
+
const FAIL = `${C.red}❌${C.reset}`;
|
|
42
|
+
const WARN = `${C.yellow}⚠️${C.reset}`;
|
|
43
|
+
const INFO = `${C.cyan}ℹ${C.reset}`;
|
|
44
|
+
|
|
45
|
+
// ─── Help ────────────────────────────────────────────────────────────────────
|
|
18
46
|
if (command === "help") {
|
|
19
47
|
console.log(`
|
|
20
|
-
nightpay — anonymous community bounties for AI agents
|
|
48
|
+
${C.bold}nightpay${C.reset} v${VERSION} — anonymous community bounties for AI agents
|
|
49
|
+
|
|
50
|
+
${C.bold}COMMANDS${C.reset}
|
|
51
|
+
npx nightpay ${C.cyan}init${C.reset} Install skill files, SDK, and setup script
|
|
52
|
+
npx nightpay ${C.cyan}setup${C.reset} Full onboarding: install + validate + platform config
|
|
53
|
+
npx nightpay ${C.cyan}validate${C.reset} Check env vars, prerequisites, and connectivity
|
|
54
|
+
npx nightpay ${C.cyan}doctor${C.reset} Diagnose and auto-fix common issues
|
|
55
|
+
npx nightpay ${C.cyan}list${C.reset} Show skill info
|
|
56
|
+
npx nightpay ${C.cyan}help${C.reset} This message
|
|
57
|
+
|
|
58
|
+
${C.bold}QUICK START${C.reset}
|
|
59
|
+
${C.dim}# Install and validate in one step:${C.reset}
|
|
60
|
+
npx nightpay setup
|
|
21
61
|
|
|
22
|
-
|
|
23
|
-
npx nightpay init
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
62
|
+
${C.dim}# Or step by step:${C.reset}
|
|
63
|
+
npx nightpay init
|
|
64
|
+
export MASUMI_API_KEY="your-key"
|
|
65
|
+
export OPERATOR_ADDRESS="your-address"
|
|
66
|
+
export NIGHTPAY_API_URL="https://api.nightpay.dev"
|
|
67
|
+
export BRIDGE_URL="https://bridge.nightpay.dev"
|
|
68
|
+
npx nightpay validate
|
|
69
|
+
|
|
70
|
+
${C.bold}WHAT INIT INSTALLS${C.reset}
|
|
71
|
+
./skills/nightpay/ Skill files (SKILL.md, scripts, config)
|
|
72
|
+
./skills/nightpay/sdk/ Python SDK (nightpay_sdk.py)
|
|
73
|
+
./skills/nightpay/scripts/ Gateway + setup scripts
|
|
27
74
|
`);
|
|
28
75
|
process.exit(0);
|
|
29
76
|
}
|
|
30
77
|
|
|
78
|
+
// ─── List ────────────────────────────────────────────────────────────────────
|
|
31
79
|
if (command === "list") {
|
|
32
80
|
console.log(`
|
|
33
|
-
Available skill
|
|
81
|
+
${C.bold}Available skill:${C.reset}
|
|
34
82
|
nightpay Anonymous community bounty board (Midnight + Masumi + Cardano)
|
|
35
83
|
Many funders pool shielded NIGHT → AI agent completes work → ZK receipt
|
|
84
|
+
|
|
85
|
+
${C.bold}Platforms:${C.reset} OpenClaw, Claude Code, Cursor, GitHub Copilot, ACP, Raw API
|
|
86
|
+
${C.bold}Version:${C.reset} ${VERSION}
|
|
87
|
+
${C.bold}License:${C.reset} Apache-2.0
|
|
36
88
|
`);
|
|
37
89
|
process.exit(0);
|
|
38
90
|
}
|
|
39
91
|
|
|
40
|
-
//
|
|
41
|
-
|
|
92
|
+
// ─── Detect platform ────────────────────────────────────────────────────────
|
|
93
|
+
function detectPlatform() {
|
|
94
|
+
try { execSync("which openclaw", { stdio: "ignore" }); return "openclaw"; } catch {}
|
|
95
|
+
if (existsSync(".claude") || existsSync(".claude/settings.json")) return "claude-code";
|
|
96
|
+
if (existsSync(".cursor") || existsSync(".cursorrules")) return "cursor";
|
|
97
|
+
if (existsSync(".github/copilot-instructions.md")) return "copilot";
|
|
98
|
+
return "raw";
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// ─── Copy one file safely ───────────────────────────────────────────────────
|
|
102
|
+
function safeCopy(src, dest, label) {
|
|
103
|
+
if (!existsSync(src)) {
|
|
104
|
+
return { status: "skip", reason: "source not found in package" };
|
|
105
|
+
}
|
|
106
|
+
mkdirSync(dirname(dest), { recursive: true });
|
|
107
|
+
if (existsSync(dest)) {
|
|
108
|
+
// Compare sizes — if same, skip
|
|
109
|
+
try {
|
|
110
|
+
const srcStat = statSync(src);
|
|
111
|
+
const destStat = statSync(dest);
|
|
112
|
+
if (srcStat.size === destStat.size) {
|
|
113
|
+
return { status: "exists", reason: "already up to date" };
|
|
114
|
+
}
|
|
115
|
+
} catch {}
|
|
116
|
+
}
|
|
117
|
+
copyFileSync(src, dest);
|
|
118
|
+
return { status: "copied" };
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// ─── Init (copy ALL files) ──────────────────────────────────────────────────
|
|
122
|
+
function init() {
|
|
123
|
+
const dest = resolve(process.cwd(), "skills", "nightpay");
|
|
124
|
+
const installed = [];
|
|
42
125
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
process.
|
|
126
|
+
console.log(`\n${C.bold}Installing NightPay${C.reset} v${VERSION}\n`);
|
|
127
|
+
|
|
128
|
+
// 1. Core skill files (SKILL.md, scripts/gateway.sh, etc.)
|
|
129
|
+
mkdirSync(resolve(process.cwd(), "skills"), { recursive: true });
|
|
130
|
+
if (existsSync(join(dest, "SKILL.md"))) {
|
|
131
|
+
// Update existing — re-copy to catch any upstream changes
|
|
132
|
+
cpSync(SKILL_SRC, dest, { recursive: true });
|
|
133
|
+
console.log(` ${OK} Skill files updated at ${C.dim}./skills/nightpay/${C.reset}`);
|
|
134
|
+
installed.push("skills/nightpay/ (updated)");
|
|
135
|
+
} else {
|
|
136
|
+
cpSync(SKILL_SRC, dest, { recursive: true });
|
|
137
|
+
console.log(` ${OK} Skill files installed to ${C.dim}./skills/nightpay/${C.reset}`);
|
|
138
|
+
installed.push("skills/nightpay/");
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// 2. Python SDK → ./skills/nightpay/sdk/nightpay_sdk.py
|
|
142
|
+
const sdkDest = join(dest, "sdk", "nightpay_sdk.py");
|
|
143
|
+
const sdkResult = safeCopy(SDK_SRC, sdkDest, "Python SDK");
|
|
144
|
+
if (sdkResult.status === "copied") {
|
|
145
|
+
console.log(` ${OK} Python SDK installed to ${C.dim}./skills/nightpay/sdk/nightpay_sdk.py${C.reset}`);
|
|
146
|
+
installed.push("sdk/nightpay_sdk.py");
|
|
147
|
+
} else if (sdkResult.status === "exists") {
|
|
148
|
+
console.log(` ${OK} Python SDK ${C.dim}(already up to date)${C.reset}`);
|
|
149
|
+
} else {
|
|
150
|
+
console.log(` ${INFO} Python SDK not bundled in this version ${C.dim}(download from GitHub)${C.reset}`);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Also copy SDK to project root for direct import convenience
|
|
154
|
+
const sdkRootDest = resolve(process.cwd(), "nightpay_sdk.py");
|
|
155
|
+
const sdkRootResult = safeCopy(SDK_SRC, sdkRootDest, "Python SDK (root)");
|
|
156
|
+
if (sdkRootResult.status === "copied") {
|
|
157
|
+
console.log(` ${OK} Python SDK also at ${C.dim}./nightpay_sdk.py${C.reset} ${C.dim}(for direct import)${C.reset}`);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// 3. Setup script → ./skills/nightpay/scripts/setup.sh
|
|
161
|
+
const setupDest = join(dest, "scripts", "setup.sh");
|
|
162
|
+
const setupResult = safeCopy(SETUP_SRC, setupDest, "setup.sh");
|
|
163
|
+
if (setupResult.status === "copied") {
|
|
164
|
+
console.log(` ${OK} Setup script installed to ${C.dim}./skills/nightpay/scripts/setup.sh${C.reset}`);
|
|
165
|
+
installed.push("scripts/setup.sh");
|
|
166
|
+
} else if (setupResult.status === "exists") {
|
|
167
|
+
console.log(` ${OK} Setup script ${C.dim}(already up to date)${C.reset}`);
|
|
168
|
+
} else {
|
|
169
|
+
console.log(` ${INFO} Setup script not bundled in this version`);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// 4. Fix permissions on ALL scripts
|
|
173
|
+
const scriptsDir = join(dest, "scripts");
|
|
174
|
+
if (existsSync(scriptsDir)) {
|
|
175
|
+
let chmodCount = 0;
|
|
176
|
+
try {
|
|
177
|
+
for (const f of readdirSync(scriptsDir)) {
|
|
178
|
+
if (f.endsWith(".sh")) {
|
|
179
|
+
chmodSync(join(scriptsDir, f), 0o755);
|
|
180
|
+
chmodCount++;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
if (chmodCount > 0) {
|
|
184
|
+
console.log(` ${OK} Made ${chmodCount} script(s) executable`);
|
|
185
|
+
}
|
|
186
|
+
} catch {}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// 5. Auto-flatten nested skill directory (common misinstall)
|
|
190
|
+
const nestedSkill = join(dest, "skills", "nightpay", "SKILL.md");
|
|
191
|
+
if (existsSync(nestedSkill)) {
|
|
192
|
+
console.log(` ${WARN} Nested skill directory detected — flattening...`);
|
|
193
|
+
cpSync(join(dest, "skills", "nightpay"), dest, { recursive: true });
|
|
194
|
+
console.log(` ${OK} Flattened: ${C.dim}skills/nightpay/skills/nightpay/ → skills/nightpay/${C.reset}`);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// 6. Summary
|
|
198
|
+
console.log(`\n${C.bold}Installed ${installed.length} component(s):${C.reset}`);
|
|
199
|
+
for (const item of installed) {
|
|
200
|
+
console.log(` ${C.dim}•${C.reset} ${item}`);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return dest;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// ─── Validate ────────────────────────────────────────────────────────────────
|
|
207
|
+
function validate() {
|
|
208
|
+
let errors = 0;
|
|
209
|
+
let warnings = 0;
|
|
210
|
+
|
|
211
|
+
console.log(`\n${C.bold}Prerequisites${C.reset}`);
|
|
212
|
+
for (const bin of ["bash", "curl", "openssl", "sqlite3"]) {
|
|
213
|
+
try {
|
|
214
|
+
execSync(`which ${bin}`, { stdio: "ignore" });
|
|
215
|
+
console.log(` ${OK} ${bin} found`);
|
|
216
|
+
} catch {
|
|
217
|
+
if (bin === "sqlite3") {
|
|
218
|
+
// sqlite3 is only needed for local receipt caching — downgrade to warning
|
|
219
|
+
console.log(` ${WARN} ${bin} not found ${C.dim}(optional — needed for local receipt caching only)${C.reset}`);
|
|
220
|
+
console.log(` ${C.dim}Fix (Debian/Ubuntu): sudo apt-get install sqlite3${C.reset}`);
|
|
221
|
+
console.log(` ${C.dim}Fix (macOS): brew install sqlite3${C.reset}`);
|
|
222
|
+
warnings++;
|
|
223
|
+
} else {
|
|
224
|
+
console.log(` ${FAIL} ${bin} not found`);
|
|
225
|
+
errors++;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
let hasHash = false;
|
|
231
|
+
try { execSync("which sha256sum", { stdio: "ignore" }); hasHash = true; } catch {}
|
|
232
|
+
try { execSync("which shasum", { stdio: "ignore" }); hasHash = true; } catch {}
|
|
233
|
+
if (hasHash) console.log(` ${OK} sha256sum/shasum found`);
|
|
234
|
+
else { console.log(` ${FAIL} sha256sum/shasum not found`); errors++; }
|
|
235
|
+
|
|
236
|
+
// Python check (for SDK)
|
|
237
|
+
let hasPython = false;
|
|
238
|
+
try { execSync("which python3", { stdio: "ignore" }); hasPython = true; } catch {}
|
|
239
|
+
if (hasPython) console.log(` ${OK} python3 found (SDK available)`);
|
|
240
|
+
else console.log(` ${INFO} python3 not found ${C.dim}(optional — needed for Python SDK)${C.reset}`);
|
|
241
|
+
|
|
242
|
+
console.log(`\n${C.bold}Environment variables${C.reset}`);
|
|
243
|
+
// Apply defaults before validation
|
|
244
|
+
const DEFAULTS = {
|
|
245
|
+
NIGHTPAY_API_URL: "https://api.nightpay.dev",
|
|
246
|
+
MIDNIGHT_NETWORK: "preprod",
|
|
247
|
+
OPERATOR_FEE_BPS: "200",
|
|
248
|
+
};
|
|
249
|
+
for (const [key, def] of Object.entries(DEFAULTS)) {
|
|
250
|
+
if (!process.env[key]) process.env[key] = def;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const required = {
|
|
254
|
+
MASUMI_API_KEY: "Masumi payment API key",
|
|
255
|
+
OPERATOR_ADDRESS: "Midnight operator address (64-char hex)",
|
|
256
|
+
NIGHTPAY_API_URL: "MIP-003 API URL",
|
|
257
|
+
BRIDGE_URL: "Midnight bridge URL",
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
for (const [key, desc] of Object.entries(required)) {
|
|
261
|
+
const val = process.env[key];
|
|
262
|
+
if (!val) {
|
|
263
|
+
console.log(` ${FAIL} ${key} not set — ${desc}`);
|
|
264
|
+
console.log(` ${C.dim}Fix: export ${key}="your-value"${C.reset}`);
|
|
265
|
+
errors++;
|
|
266
|
+
} else if (val === key) {
|
|
267
|
+
console.log(` ${FAIL} ${key} is placeholder "${key}" — replace with real value`);
|
|
268
|
+
errors++;
|
|
269
|
+
} else if (key === "OPERATOR_ADDRESS" && (val.length !== 64 || !/^[0-9a-fA-F]+$/.test(val))) {
|
|
270
|
+
console.log(` ${WARN} ${key} doesn't look like 64-char hex (got ${val.length} chars)`);
|
|
271
|
+
warnings++;
|
|
272
|
+
} else {
|
|
273
|
+
const display = key.includes("URL") ? val : `${val.slice(0, 8)}...`;
|
|
274
|
+
console.log(` ${OK} ${key} set (${display})`);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
console.log(`\n${C.bold}Skill files${C.reset}`);
|
|
279
|
+
const dest = resolve(process.cwd(), "skills", "nightpay");
|
|
280
|
+
|
|
281
|
+
const fileChecks = [
|
|
282
|
+
{ path: join(dest, "SKILL.md"), label: "SKILL.md", required: true },
|
|
283
|
+
{ path: join(dest, "scripts", "gateway.sh"), label: "gateway.sh", required: true },
|
|
284
|
+
{ path: join(dest, "scripts", "setup.sh"), label: "setup.sh", required: false },
|
|
285
|
+
{ path: join(dest, "sdk", "nightpay_sdk.py"), label: "Python SDK (sdk/)", required: false },
|
|
286
|
+
];
|
|
287
|
+
|
|
288
|
+
for (const check of fileChecks) {
|
|
289
|
+
if (existsSync(check.path)) {
|
|
290
|
+
console.log(` ${OK} ${check.label} found`);
|
|
291
|
+
} else if (check.required) {
|
|
292
|
+
console.log(` ${FAIL} ${check.label} not found — run: ${C.cyan}npx nightpay init${C.reset}`);
|
|
293
|
+
errors++;
|
|
294
|
+
} else {
|
|
295
|
+
console.log(` ${WARN} ${check.label} not found — run: ${C.cyan}npx nightpay init${C.reset} to install`);
|
|
296
|
+
warnings++;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// Check for root-level SDK copy too
|
|
301
|
+
const rootSdk = resolve(process.cwd(), "nightpay_sdk.py");
|
|
302
|
+
if (existsSync(rootSdk)) {
|
|
303
|
+
console.log(` ${OK} Python SDK also at ./nightpay_sdk.py`);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
console.log(`\n${C.bold}Connectivity${C.reset}`);
|
|
307
|
+
const apiUrl = process.env.NIGHTPAY_API_URL;
|
|
308
|
+
if (apiUrl && apiUrl !== "NIGHTPAY_API_URL") {
|
|
309
|
+
try {
|
|
310
|
+
execSync(`curl -sf --max-time 10 "${apiUrl}/availability"`, { stdio: "ignore" });
|
|
311
|
+
console.log(` ${OK} API reachable at ${apiUrl}`);
|
|
312
|
+
} catch {
|
|
313
|
+
console.log(` ${WARN} API unreachable at ${apiUrl}`);
|
|
314
|
+
warnings++;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
const bridgeUrl = process.env.BRIDGE_URL;
|
|
319
|
+
if (bridgeUrl && bridgeUrl !== "BRIDGE_URL") {
|
|
320
|
+
try {
|
|
321
|
+
execSync(`curl -sf --max-time 10 "${bridgeUrl}/health"`, { stdio: "ignore" });
|
|
322
|
+
console.log(` ${OK} Bridge reachable at ${bridgeUrl}`);
|
|
323
|
+
} catch {
|
|
324
|
+
console.log(` ${WARN} Bridge unreachable at ${bridgeUrl}`);
|
|
325
|
+
warnings++;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// Summary
|
|
330
|
+
console.log("");
|
|
331
|
+
if (errors === 0 && warnings === 0) {
|
|
332
|
+
console.log(`${C.green}${C.bold}NightPay is ready!${C.reset} Run: bash skills/nightpay/scripts/gateway.sh stats`);
|
|
333
|
+
} else if (errors === 0) {
|
|
334
|
+
console.log(`${C.yellow}${C.bold}Ready with ${warnings} warning(s)${C.reset} — review above`);
|
|
335
|
+
} else {
|
|
336
|
+
console.log(`${C.red}${C.bold}Not ready: ${errors} error(s), ${warnings} warning(s)${C.reset}`);
|
|
337
|
+
console.log(`Fix the issues above and run: npx nightpay validate`);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
return { errors, warnings };
|
|
47
341
|
}
|
|
48
342
|
|
|
49
|
-
|
|
50
|
-
|
|
343
|
+
// ─── Doctor (diagnose + auto-fix) ────────────────────────────────────────────
|
|
344
|
+
function doctor() {
|
|
345
|
+
console.log(`\n${C.bold}NightPay Doctor${C.reset} v${VERSION} — diagnosing and fixing issues...\n`);
|
|
346
|
+
let fixed = 0;
|
|
347
|
+
|
|
348
|
+
const dest = resolve(process.cwd(), "skills", "nightpay");
|
|
349
|
+
|
|
350
|
+
// Fix 1: Missing skill files → full init
|
|
351
|
+
if (!existsSync(join(dest, "SKILL.md"))) {
|
|
352
|
+
console.log(` ${WARN} Skill not installed — running full init...`);
|
|
353
|
+
init();
|
|
354
|
+
fixed++;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// Fix 2: Nested SKILL.md
|
|
358
|
+
const nestedSkill = join(dest, "skills", "nightpay", "SKILL.md");
|
|
359
|
+
if (existsSync(nestedSkill)) {
|
|
360
|
+
console.log(` ${WARN} SKILL.md nested — flattening...`);
|
|
361
|
+
cpSync(join(dest, "skills", "nightpay"), dest, { recursive: true });
|
|
362
|
+
console.log(` ${OK} Fixed: flattened skill directory`);
|
|
363
|
+
fixed++;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// Fix 3: Script permissions
|
|
367
|
+
const scriptsDir = join(dest, "scripts");
|
|
368
|
+
if (existsSync(scriptsDir)) {
|
|
369
|
+
for (const f of readdirSync(scriptsDir)) {
|
|
370
|
+
if (f.endsWith(".sh")) {
|
|
371
|
+
try {
|
|
372
|
+
chmodSync(join(scriptsDir, f), 0o755);
|
|
373
|
+
fixed++;
|
|
374
|
+
} catch {}
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
console.log(` ${OK} Fixed: script permissions`);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// Fix 4: Missing SDK
|
|
381
|
+
const sdkDest = join(dest, "sdk", "nightpay_sdk.py");
|
|
382
|
+
if (!existsSync(sdkDest) && existsSync(SDK_SRC)) {
|
|
383
|
+
mkdirSync(join(dest, "sdk"), { recursive: true });
|
|
384
|
+
copyFileSync(SDK_SRC, sdkDest);
|
|
385
|
+
console.log(` ${OK} Fixed: installed Python SDK to ${C.dim}sdk/nightpay_sdk.py${C.reset}`);
|
|
386
|
+
fixed++;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// Fix 5: Missing setup.sh
|
|
390
|
+
const setupDest = join(dest, "scripts", "setup.sh");
|
|
391
|
+
if (!existsSync(setupDest) && existsSync(SETUP_SRC)) {
|
|
392
|
+
mkdirSync(join(dest, "scripts"), { recursive: true });
|
|
393
|
+
copyFileSync(SETUP_SRC, setupDest);
|
|
394
|
+
chmodSync(setupDest, 0o755);
|
|
395
|
+
console.log(` ${OK} Fixed: installed setup.sh to ${C.dim}scripts/setup.sh${C.reset}`);
|
|
396
|
+
fixed++;
|
|
397
|
+
}
|
|
51
398
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
console.log(`
|
|
399
|
+
// Fix 6: Root SDK convenience copy
|
|
400
|
+
const rootSdk = resolve(process.cwd(), "nightpay_sdk.py");
|
|
401
|
+
if (!existsSync(rootSdk) && existsSync(SDK_SRC)) {
|
|
402
|
+
copyFileSync(SDK_SRC, rootSdk);
|
|
403
|
+
console.log(` ${OK} Fixed: copied SDK to ${C.dim}./nightpay_sdk.py${C.reset} for direct import`);
|
|
404
|
+
fixed++;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// Fix 7: Warn about placeholder env vars
|
|
408
|
+
const fragment = join(dest, "openclaw-fragment.json");
|
|
409
|
+
if (existsSync(fragment)) {
|
|
410
|
+
try {
|
|
411
|
+
const content = readFileSync(fragment, "utf8");
|
|
412
|
+
const data = JSON.parse(content);
|
|
413
|
+
const env = data?.skills?.entries?.nightpay?.env || {};
|
|
414
|
+
const placeholders = Object.entries(env).filter(([k, v]) => k === v);
|
|
415
|
+
if (placeholders.length > 0) {
|
|
416
|
+
console.log(`\n ${WARN} openclaw-fragment.json has ${placeholders.length} placeholder value(s):`);
|
|
417
|
+
for (const [k] of placeholders) {
|
|
418
|
+
console.log(` ${C.dim}${k}: "${k}" → replace with real value${C.reset}`);
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
} catch {}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
console.log(`\n Applied ${fixed} fix(es). Running validation...\n`);
|
|
425
|
+
validate();
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
// ─── Setup (full onboarding) ─────────────────────────────────────────────────
|
|
429
|
+
function setup() {
|
|
430
|
+
const platform = detectPlatform();
|
|
431
|
+
console.log(`\n${C.bold}NightPay Agent Onboarding${C.reset} v${VERSION}`);
|
|
432
|
+
console.log(`${C.dim}Anonymous community bounties for AI agents${C.reset}`);
|
|
433
|
+
console.log(`\n Platform: ${C.bold}${platform}${C.reset}\n`);
|
|
434
|
+
|
|
435
|
+
// Step 1: Smart install (all files)
|
|
436
|
+
const dest = init();
|
|
437
|
+
|
|
438
|
+
// Step 2: Platform-specific config
|
|
439
|
+
console.log(`\n${C.bold}Platform config (${platform})${C.reset}`);
|
|
440
|
+
|
|
441
|
+
if (platform === "claude-code") {
|
|
442
|
+
const cmdDir = resolve(process.cwd(), ".claude", "commands");
|
|
443
|
+
const cmdFile = join(cmdDir, "nightpay.md");
|
|
444
|
+
if (!existsSync(cmdFile)) {
|
|
445
|
+
mkdirSync(cmdDir, { recursive: true });
|
|
446
|
+
writeFileSync(cmdFile, [
|
|
447
|
+
"# NightPay",
|
|
448
|
+
"",
|
|
449
|
+
"Use the nightpay skill at ./skills/nightpay/ for bounty operations.",
|
|
450
|
+
"",
|
|
451
|
+
"## Quick commands",
|
|
452
|
+
"- `bash skills/nightpay/scripts/gateway.sh stats` — contract stats",
|
|
453
|
+
"- `bash skills/nightpay/scripts/gateway.sh post-bounty \"<desc>\" <amount>` — post bounty",
|
|
454
|
+
"- `python3 skills/nightpay/sdk/nightpay_sdk.py validate` — health check",
|
|
455
|
+
"- `python3 skills/nightpay/sdk/nightpay_sdk.py doctor --auto-fix` — self-heal",
|
|
456
|
+
"",
|
|
457
|
+
].join("\n"));
|
|
458
|
+
console.log(` ${OK} Created ${C.dim}.claude/commands/nightpay.md${C.reset}`);
|
|
459
|
+
} else {
|
|
460
|
+
console.log(` ${OK} ${C.dim}.claude/commands/nightpay.md${C.reset} already exists`);
|
|
461
|
+
}
|
|
462
|
+
} else if (platform === "cursor") {
|
|
463
|
+
const rulesDir = resolve(process.cwd(), ".cursor", "rules");
|
|
464
|
+
const rulesFile = join(rulesDir, "nightpay.md");
|
|
465
|
+
if (!existsSync(rulesFile)) {
|
|
466
|
+
mkdirSync(rulesDir, { recursive: true });
|
|
467
|
+
writeFileSync(rulesFile, [
|
|
468
|
+
"# NightPay Skill",
|
|
469
|
+
"",
|
|
470
|
+
"The nightpay skill is at ./skills/nightpay/. Read SKILL.md for capabilities.",
|
|
471
|
+
"Python SDK at ./skills/nightpay/sdk/nightpay_sdk.py or ./nightpay_sdk.py.",
|
|
472
|
+
"",
|
|
473
|
+
"Quick: `bash skills/nightpay/scripts/gateway.sh stats`",
|
|
474
|
+
"",
|
|
475
|
+
].join("\n"));
|
|
476
|
+
console.log(` ${OK} Created ${C.dim}.cursor/rules/nightpay.md${C.reset}`);
|
|
477
|
+
} else {
|
|
478
|
+
console.log(` ${OK} ${C.dim}.cursor/rules/nightpay.md${C.reset} already exists`);
|
|
479
|
+
}
|
|
480
|
+
} else if (platform === "copilot") {
|
|
481
|
+
const instrFile = resolve(process.cwd(), ".github", "copilot-instructions.md");
|
|
482
|
+
if (existsSync(instrFile)) {
|
|
483
|
+
const content = readFileSync(instrFile, "utf8");
|
|
484
|
+
if (!content.includes("nightpay")) {
|
|
485
|
+
const addition = [
|
|
486
|
+
"",
|
|
487
|
+
"## NightPay",
|
|
488
|
+
"",
|
|
489
|
+
"Bounty skill at ./skills/nightpay/. Read SKILL.md for full capabilities.",
|
|
490
|
+
"Python SDK at ./skills/nightpay/sdk/nightpay_sdk.py.",
|
|
491
|
+
"Gateway: `bash skills/nightpay/scripts/gateway.sh`",
|
|
492
|
+
"",
|
|
493
|
+
].join("\n");
|
|
494
|
+
writeFileSync(instrFile, content + addition);
|
|
495
|
+
console.log(` ${OK} Appended NightPay section to ${C.dim}.github/copilot-instructions.md${C.reset}`);
|
|
496
|
+
} else {
|
|
497
|
+
console.log(` ${OK} Copilot instructions already mention nightpay`);
|
|
498
|
+
}
|
|
499
|
+
} else {
|
|
500
|
+
console.log(` ${INFO} No .github/copilot-instructions.md — skipping Copilot config`);
|
|
501
|
+
}
|
|
502
|
+
} else if (platform === "openclaw") {
|
|
503
|
+
// Check if already installed as plugin
|
|
504
|
+
let pluginInstalled = false;
|
|
505
|
+
try {
|
|
506
|
+
const result = spawnSync("openclaw", ["plugins", "list", "--json"], { encoding: "utf8", stdio: "pipe" });
|
|
507
|
+
if (result.stdout && result.stdout.includes('"nightpay"')) pluginInstalled = true;
|
|
508
|
+
} catch {}
|
|
509
|
+
|
|
510
|
+
if (pluginInstalled) {
|
|
511
|
+
console.log(` ${OK} NightPay plugin already installed in OpenClaw`);
|
|
512
|
+
console.log(` ${C.dim} Run: openclaw plugins enable nightpay${C.reset}`);
|
|
513
|
+
} else {
|
|
514
|
+
console.log(` ${INFO} Installing NightPay as OpenClaw plugin...`);
|
|
515
|
+
const installResult = spawnSync("openclaw", ["plugins", "install", "nightpay"], { encoding: "utf8", stdio: "inherit" });
|
|
516
|
+
if (installResult.status === 0) {
|
|
517
|
+
console.log(` ${OK} Installed! Run: ${C.cyan}openclaw plugins enable nightpay${C.reset}`);
|
|
518
|
+
} else {
|
|
519
|
+
console.log(` ${WARN} Plugin install failed — falling back to skill files`);
|
|
520
|
+
console.log(` ${C.dim} Skill files are at ./skills/nightpay/ (already installed above)${C.reset}`);
|
|
521
|
+
console.log(` ${C.dim} Merge ./skills/nightpay/openclaw-fragment.json into openclaw.json${C.reset}`);
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
console.log(` ${C.dim} Set env: openclaw config set skills.entries.nightpay.env.MASUMI_API_KEY "your-key"${C.reset}`);
|
|
525
|
+
} else {
|
|
526
|
+
console.log(` ${INFO} Raw platform — no config file needed`);
|
|
527
|
+
console.log(` ${C.dim} Use: bash skills/nightpay/scripts/gateway.sh <command>${C.reset}`);
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
// Step 3: Run validate
|
|
531
|
+
console.log("");
|
|
532
|
+
const { errors } = validate();
|
|
533
|
+
|
|
534
|
+
// Step 4: Next steps
|
|
535
|
+
console.log(`\n${C.bold}Next steps${C.reset}`);
|
|
536
|
+
if (errors > 0) {
|
|
537
|
+
console.log(` 1. Fix the ${errors} error(s) above`);
|
|
538
|
+
console.log(` 2. Run: ${C.cyan}npx nightpay validate${C.reset}`);
|
|
539
|
+
} else {
|
|
540
|
+
console.log(` 1. ${C.cyan}bash skills/nightpay/scripts/gateway.sh stats${C.reset} — check contract`);
|
|
541
|
+
console.log(` 2. ${C.cyan}bash skills/nightpay/scripts/gateway.sh post-bounty "Review this PR" 5000${C.reset}`);
|
|
542
|
+
}
|
|
543
|
+
console.log(`\n ${C.dim}Python SDK:${C.reset} from nightpay_sdk import NightPay; NightPay().stats()`);
|
|
544
|
+
console.log(` ${C.dim}Self-heal:${C.reset} npx nightpay doctor`);
|
|
545
|
+
console.log("");
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
// ─── Route command ───────────────────────────────────────────────────────────
|
|
549
|
+
if (command === "init" || command === "add") {
|
|
550
|
+
init();
|
|
551
|
+
console.log(`\nNext: run ${C.cyan}npx nightpay validate${C.reset} to check your setup`);
|
|
552
|
+
} else if (command === "setup") {
|
|
553
|
+
setup();
|
|
554
|
+
} else if (command === "validate") {
|
|
555
|
+
const { errors } = validate();
|
|
556
|
+
process.exit(errors > 0 ? 1 : 0);
|
|
557
|
+
} else if (command === "doctor") {
|
|
558
|
+
doctor();
|
|
559
|
+
}
|