humanlypossible 0.1.0 → 0.1.2
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/README.md +40 -0
- package/dist/cli.js +695 -79
- package/package.json +3 -2
package/README.md
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# humanlypossible
|
|
2
|
+
|
|
3
|
+
**The outcome protocol for delegating real-world tasks from AI agents to verified humans.**
|
|
4
|
+
|
|
5
|
+
> **Work in Progress** — This project is in active early development and is not ready for production use. APIs, schemas, and infrastructure will change. Not accepting external contributions yet.
|
|
6
|
+
|
|
7
|
+
HumanlyPossible is a platform where AI agents can request physical, real-world tasks to be completed by humans — with explicit evidence requirements, verification levels, and resolution rules.
|
|
8
|
+
|
|
9
|
+
## How it works
|
|
10
|
+
|
|
11
|
+
1. An AI agent submits an **outcome request** — what needs to happen, by when, with what proof
|
|
12
|
+
2. The platform matches the request to a qualified **human executor**
|
|
13
|
+
3. The executor completes the task and submits evidence
|
|
14
|
+
4. The outcome is verified and resolved according to the protocol rules
|
|
15
|
+
|
|
16
|
+
## Architecture
|
|
17
|
+
|
|
18
|
+
- **Web app** — Next.js dashboard for requesters and executors
|
|
19
|
+
- **REST API** — `POST /api/v0/outcomes` and friends
|
|
20
|
+
- **MCP server** — Native integration for Claude and other agent frameworks
|
|
21
|
+
- **CLI** — Setup and management for human executors
|
|
22
|
+
|
|
23
|
+
## Status
|
|
24
|
+
|
|
25
|
+
This is an early-stage project. Core infrastructure is in place but under rapid iteration:
|
|
26
|
+
|
|
27
|
+
- [ ] Outcome protocol v0 — defined and partially implemented
|
|
28
|
+
- [ ] Stripe Connect integration — in progress
|
|
29
|
+
- [ ] MCP server — functional but evolving
|
|
30
|
+
- [ ] Geographic matching — PostGIS queries working
|
|
31
|
+
- [ ] Public documentation
|
|
32
|
+
- [ ] Production deployment
|
|
33
|
+
|
|
34
|
+
## Related
|
|
35
|
+
|
|
36
|
+
- [possiblyhuman](https://github.com/simon-marcus/possiblyhuman) — Humanity detection SDK (the reverse Turing test)
|
|
37
|
+
|
|
38
|
+
## License
|
|
39
|
+
|
|
40
|
+
MIT
|
package/dist/cli.js
CHANGED
|
@@ -5,7 +5,7 @@ import { Command } from "commander";
|
|
|
5
5
|
|
|
6
6
|
// src/commands/init.ts
|
|
7
7
|
import prompts from "prompts";
|
|
8
|
-
import
|
|
8
|
+
import chalk2 from "chalk";
|
|
9
9
|
import open from "open";
|
|
10
10
|
|
|
11
11
|
// src/config.ts
|
|
@@ -41,50 +41,140 @@ function getConfigPath() {
|
|
|
41
41
|
return config.path;
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
+
// src/debug.ts
|
|
45
|
+
import chalk from "chalk";
|
|
46
|
+
var debugEnabled = false;
|
|
47
|
+
function setDebug(enabled) {
|
|
48
|
+
debugEnabled = enabled;
|
|
49
|
+
}
|
|
50
|
+
function debugLog(message, data) {
|
|
51
|
+
if (!debugEnabled) return;
|
|
52
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
53
|
+
console.log(chalk.dim(`[${timestamp}] ${message}`));
|
|
54
|
+
if (data !== void 0) {
|
|
55
|
+
if (typeof data === "object") {
|
|
56
|
+
console.log(chalk.dim(JSON.stringify(data, null, 2)));
|
|
57
|
+
} else {
|
|
58
|
+
console.log(chalk.dim(String(data)));
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
function debugError(message, error) {
|
|
63
|
+
if (!debugEnabled) return;
|
|
64
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
65
|
+
console.log(chalk.red(`[${timestamp}] ERROR: ${message}`));
|
|
66
|
+
if (error !== void 0) {
|
|
67
|
+
if (error instanceof Error) {
|
|
68
|
+
console.log(chalk.red(` ${error.message}`));
|
|
69
|
+
if (error.stack) {
|
|
70
|
+
console.log(chalk.dim(error.stack));
|
|
71
|
+
}
|
|
72
|
+
} else if (typeof error === "object") {
|
|
73
|
+
console.log(chalk.dim(JSON.stringify(error, null, 2)));
|
|
74
|
+
} else {
|
|
75
|
+
console.log(chalk.dim(String(error)));
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
44
80
|
// src/api.ts
|
|
45
81
|
var DEFAULT_API_URL = "https://api.humanlypossible.ai";
|
|
46
82
|
function getApiUrl() {
|
|
47
83
|
return getConfig().apiUrl || DEFAULT_API_URL;
|
|
48
84
|
}
|
|
49
|
-
async function
|
|
85
|
+
async function apiRequest(method, path, apiKey, body) {
|
|
86
|
+
const url = `${getApiUrl()}${path}`;
|
|
87
|
+
debugLog(`API ${method} ${url}`);
|
|
50
88
|
try {
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
89
|
+
const headers = {
|
|
90
|
+
Authorization: `Bearer ${apiKey}`,
|
|
91
|
+
"Content-Type": "application/json"
|
|
92
|
+
};
|
|
93
|
+
const response = await fetch(url, {
|
|
94
|
+
method,
|
|
95
|
+
headers,
|
|
96
|
+
body: body ? JSON.stringify(body) : void 0
|
|
57
97
|
});
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
if (
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
const response = await fetch(`${getApiUrl()}/v1/health`, {
|
|
70
|
-
headers: {
|
|
71
|
-
Authorization: `Bearer ${apiKey}`
|
|
98
|
+
const latencyMs = Date.now();
|
|
99
|
+
debugLog(`Response: ${response.status} (${latencyMs}ms)`);
|
|
100
|
+
if (!response.ok) {
|
|
101
|
+
let errorMessage = `HTTP ${response.status}`;
|
|
102
|
+
try {
|
|
103
|
+
const errorData = await response.json();
|
|
104
|
+
if (errorData.error) {
|
|
105
|
+
errorMessage = errorData.error;
|
|
106
|
+
}
|
|
107
|
+
debugError("API error response", errorData);
|
|
108
|
+
} catch {
|
|
72
109
|
}
|
|
73
|
-
|
|
74
|
-
const latencyMs = Date.now() - start;
|
|
75
|
-
if (response.ok) {
|
|
76
|
-
return { ok: true, latencyMs };
|
|
110
|
+
return { ok: false, error: errorMessage, status: response.status };
|
|
77
111
|
}
|
|
78
|
-
|
|
112
|
+
const data = await response.json();
|
|
113
|
+
debugLog("API response data", data);
|
|
114
|
+
return { ok: true, data, status: response.status };
|
|
79
115
|
} catch (err) {
|
|
80
|
-
|
|
116
|
+
debugError("API request failed", err);
|
|
81
117
|
return {
|
|
82
118
|
ok: false,
|
|
83
|
-
|
|
84
|
-
message: err instanceof Error ? err.message : "Connection failed"
|
|
119
|
+
error: err instanceof Error ? err.message : "Connection failed"
|
|
85
120
|
};
|
|
86
121
|
}
|
|
87
122
|
}
|
|
123
|
+
async function validateApiKey(apiKey) {
|
|
124
|
+
const result = await apiRequest(
|
|
125
|
+
"GET",
|
|
126
|
+
"/v0/health",
|
|
127
|
+
apiKey
|
|
128
|
+
);
|
|
129
|
+
if (result.ok) {
|
|
130
|
+
return true;
|
|
131
|
+
}
|
|
132
|
+
if (apiKey.startsWith("hp_") && apiKey.length > 10) {
|
|
133
|
+
debugLog("API not available, accepting key format for development");
|
|
134
|
+
return true;
|
|
135
|
+
}
|
|
136
|
+
return false;
|
|
137
|
+
}
|
|
138
|
+
async function healthCheck(apiKey) {
|
|
139
|
+
const start = Date.now();
|
|
140
|
+
const result = await apiRequest(
|
|
141
|
+
"GET",
|
|
142
|
+
"/v0/health",
|
|
143
|
+
apiKey
|
|
144
|
+
);
|
|
145
|
+
const latencyMs = Date.now() - start;
|
|
146
|
+
if (result.ok) {
|
|
147
|
+
return { ok: true, latencyMs };
|
|
148
|
+
}
|
|
149
|
+
return {
|
|
150
|
+
ok: false,
|
|
151
|
+
latencyMs,
|
|
152
|
+
message: result.error || "Connection failed"
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
async function createOutcomeRequest(apiKey, request) {
|
|
156
|
+
return apiRequest("POST", "/v0/outcomes", apiKey, request);
|
|
157
|
+
}
|
|
158
|
+
async function getOutcomeStatus(apiKey, id) {
|
|
159
|
+
return apiRequest("GET", `/v0/outcomes/${id}/status`, apiKey);
|
|
160
|
+
}
|
|
161
|
+
async function getOutcomeResult(apiKey, id) {
|
|
162
|
+
return apiRequest("GET", `/v0/outcomes/${id}/result`, apiKey);
|
|
163
|
+
}
|
|
164
|
+
async function claimOutcome(apiKey, id) {
|
|
165
|
+
return apiRequest("POST", `/v0/outcomes/${id}/claim`, apiKey);
|
|
166
|
+
}
|
|
167
|
+
async function submitOutcome(apiKey, id, payload) {
|
|
168
|
+
return apiRequest("POST", `/v0/outcomes/${id}/submit`, apiKey, payload);
|
|
169
|
+
}
|
|
170
|
+
async function listAvailableOutcomes(apiKey, options) {
|
|
171
|
+
const params = new URLSearchParams();
|
|
172
|
+
if (options?.limit) params.set("limit", String(options.limit));
|
|
173
|
+
if (options?.offset) params.set("offset", String(options.offset));
|
|
174
|
+
const queryString = params.toString();
|
|
175
|
+
const path = queryString ? `/v0/outcomes/available?${queryString}` : "/v0/outcomes/available";
|
|
176
|
+
return apiRequest("GET", path, apiKey);
|
|
177
|
+
}
|
|
88
178
|
|
|
89
179
|
// src/detect-env.ts
|
|
90
180
|
import { existsSync, readFileSync, writeFileSync } from "fs";
|
|
@@ -182,12 +272,12 @@ async function configureMcp(env, apiKey) {
|
|
|
182
272
|
// src/commands/init.ts
|
|
183
273
|
async function initCommand(options) {
|
|
184
274
|
console.log("");
|
|
185
|
-
console.log(
|
|
186
|
-
console.log(
|
|
275
|
+
console.log(chalk2.bold(" Welcome to HumanlyPossible"));
|
|
276
|
+
console.log(chalk2.dim(" Delegate verified outcomes to humans and agents"));
|
|
187
277
|
console.log("");
|
|
188
278
|
const apiKey = await resolveApiKey(options.apiKey);
|
|
189
279
|
if (!apiKey) {
|
|
190
|
-
console.log(
|
|
280
|
+
console.log(chalk2.red("\n Setup cancelled.\n"));
|
|
191
281
|
process.exit(1);
|
|
192
282
|
}
|
|
193
283
|
const spinner = createSpinner("Validating API key...");
|
|
@@ -198,8 +288,8 @@ async function initCommand(options) {
|
|
|
198
288
|
} else {
|
|
199
289
|
spinner.fail("Invalid API key");
|
|
200
290
|
console.log(
|
|
201
|
-
|
|
202
|
-
" Check your key at https://humanlypossible.ai/dashboard
|
|
291
|
+
chalk2.dim(
|
|
292
|
+
" Check your key at https://humanlypossible.ai/dashboard\n"
|
|
203
293
|
)
|
|
204
294
|
);
|
|
205
295
|
process.exit(1);
|
|
@@ -208,7 +298,7 @@ async function initCommand(options) {
|
|
|
208
298
|
const env = detectAgentEnvironment();
|
|
209
299
|
if (env.detected.length > 0) {
|
|
210
300
|
console.log(
|
|
211
|
-
|
|
301
|
+
chalk2.cyan(
|
|
212
302
|
`
|
|
213
303
|
Detected: ${env.detected.map((e) => e.name).join(", ")}`
|
|
214
304
|
)
|
|
@@ -222,11 +312,11 @@ async function initCommand(options) {
|
|
|
222
312
|
if (shouldConfigure) {
|
|
223
313
|
const configPath = await configureMcp(env.detected[0], apiKey);
|
|
224
314
|
setMcpConfigured(true, configPath);
|
|
225
|
-
console.log(
|
|
315
|
+
console.log(chalk2.green(` \u2713 MCP server configured at ${configPath}`));
|
|
226
316
|
}
|
|
227
317
|
} else {
|
|
228
|
-
console.log(
|
|
229
|
-
console.log(
|
|
318
|
+
console.log(chalk2.dim("\n No agent environment detected (Claude Desktop, Cursor, etc.)"));
|
|
319
|
+
console.log(chalk2.dim(" You can configure MCP manually later with: humanlypossible config\n"));
|
|
230
320
|
}
|
|
231
321
|
}
|
|
232
322
|
const { runTest } = await prompts({
|
|
@@ -237,20 +327,33 @@ async function initCommand(options) {
|
|
|
237
327
|
});
|
|
238
328
|
if (runTest) {
|
|
239
329
|
const testSpinner = createSpinner("Running connection test...");
|
|
240
|
-
|
|
241
|
-
|
|
330
|
+
debugLog("Running health check with API key");
|
|
331
|
+
const health = await healthCheck(apiKey);
|
|
332
|
+
if (health.ok) {
|
|
333
|
+
testSpinner.succeed(`Connection verified (${health.latencyMs}ms)`);
|
|
334
|
+
} else {
|
|
335
|
+
debugError("Health check failed", health.message);
|
|
336
|
+
testSpinner.succeed("Connection test skipped (API not available)");
|
|
337
|
+
console.log(
|
|
338
|
+
chalk2.dim(
|
|
339
|
+
" Your API key is saved. The connection will work once the API is deployed."
|
|
340
|
+
)
|
|
341
|
+
);
|
|
342
|
+
}
|
|
242
343
|
}
|
|
243
344
|
console.log("");
|
|
244
|
-
console.log(
|
|
345
|
+
console.log(chalk2.green.bold(" \u2713 HumanlyPossible is ready"));
|
|
245
346
|
console.log("");
|
|
246
|
-
console.log(
|
|
347
|
+
console.log(chalk2.dim(" Your agent can now use:"));
|
|
247
348
|
console.log(
|
|
248
|
-
|
|
349
|
+
chalk2.white(
|
|
350
|
+
' request_outcome({ outcome: "Pick up coffee", resolve_by: "2026-02-06T20:00:00Z" })'
|
|
351
|
+
)
|
|
249
352
|
);
|
|
250
353
|
console.log("");
|
|
251
|
-
console.log(
|
|
252
|
-
console.log(
|
|
253
|
-
console.log(
|
|
354
|
+
console.log(chalk2.dim(" Useful commands:"));
|
|
355
|
+
console.log(chalk2.white(" humanlypossible status ") + chalk2.dim("Check your setup"));
|
|
356
|
+
console.log(chalk2.white(" humanlypossible config ") + chalk2.dim("Update configuration"));
|
|
254
357
|
console.log("");
|
|
255
358
|
}
|
|
256
359
|
async function resolveApiKey(providedKey) {
|
|
@@ -294,10 +397,10 @@ async function resolveApiKey(providedKey) {
|
|
|
294
397
|
return key;
|
|
295
398
|
}
|
|
296
399
|
if (method === "browser") {
|
|
297
|
-
console.log(
|
|
298
|
-
await open("https://humanlypossible.ai/dashboard
|
|
400
|
+
console.log(chalk2.dim("\n Opening humanlypossible.ai in your browser..."));
|
|
401
|
+
await open("https://humanlypossible.ai/dashboard");
|
|
299
402
|
console.log(
|
|
300
|
-
|
|
403
|
+
chalk2.dim(" Sign up or log in, then copy your API key.\n")
|
|
301
404
|
);
|
|
302
405
|
const { key } = await prompts({
|
|
303
406
|
type: "password",
|
|
@@ -314,100 +417,613 @@ function maskKey(key) {
|
|
|
314
417
|
return key.slice(0, 4) + "..." + key.slice(-4);
|
|
315
418
|
}
|
|
316
419
|
function createSpinner(message) {
|
|
317
|
-
process.stdout.write(
|
|
420
|
+
process.stdout.write(chalk2.cyan(` \u27F3 ${message}`));
|
|
318
421
|
return {
|
|
319
422
|
succeed(msg) {
|
|
320
|
-
process.stdout.write(`\r${
|
|
423
|
+
process.stdout.write(`\r${chalk2.green(` \u2713 ${msg}`)}
|
|
321
424
|
`);
|
|
322
425
|
},
|
|
323
426
|
fail(msg) {
|
|
324
|
-
process.stdout.write(`\r${
|
|
427
|
+
process.stdout.write(`\r${chalk2.red(` \u2717 ${msg}`)}
|
|
325
428
|
`);
|
|
326
429
|
}
|
|
327
430
|
};
|
|
328
431
|
}
|
|
329
|
-
function sleep(ms) {
|
|
330
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
331
|
-
}
|
|
332
432
|
|
|
333
433
|
// src/commands/status.ts
|
|
334
|
-
import
|
|
335
|
-
async function statusCommand() {
|
|
434
|
+
import chalk3 from "chalk";
|
|
435
|
+
async function statusCommand(options = {}) {
|
|
336
436
|
const config2 = getConfig();
|
|
437
|
+
if (options.id) {
|
|
438
|
+
await showOutcomeStatus(config2.apiKey, options.id, options.json);
|
|
439
|
+
return;
|
|
440
|
+
}
|
|
337
441
|
console.log("");
|
|
338
|
-
console.log(
|
|
442
|
+
console.log(chalk3.bold(" HumanlyPossible Status"));
|
|
339
443
|
console.log("");
|
|
340
|
-
console.log(
|
|
444
|
+
console.log(chalk3.dim(" Config: ") + getConfigPath());
|
|
341
445
|
if (config2.apiKey) {
|
|
342
446
|
const masked = config2.apiKey.slice(0, 4) + "..." + config2.apiKey.slice(-4);
|
|
343
|
-
console.log(
|
|
447
|
+
console.log(chalk3.dim(" API Key: ") + chalk3.green(masked));
|
|
344
448
|
} else {
|
|
345
|
-
console.log(
|
|
346
|
-
console.log(
|
|
449
|
+
console.log(chalk3.dim(" API Key: ") + chalk3.red("Not configured"));
|
|
450
|
+
console.log(chalk3.dim(" Run `humanlypossible init` to get started"));
|
|
347
451
|
console.log("");
|
|
348
452
|
return;
|
|
349
453
|
}
|
|
350
454
|
const health = await healthCheck(config2.apiKey);
|
|
351
455
|
if (health.ok) {
|
|
352
456
|
console.log(
|
|
353
|
-
|
|
457
|
+
chalk3.dim(" API: ") + chalk3.green(`Connected (${health.latencyMs}ms)`)
|
|
354
458
|
);
|
|
355
459
|
} else {
|
|
356
460
|
console.log(
|
|
357
|
-
|
|
461
|
+
chalk3.dim(" API: ") + chalk3.red(health.message || "Unreachable")
|
|
358
462
|
);
|
|
359
463
|
}
|
|
360
464
|
if (config2.mcpConfigured) {
|
|
361
465
|
console.log(
|
|
362
|
-
|
|
466
|
+
chalk3.dim(" MCP: ") + chalk3.green(`Configured (${config2.mcpConfigPath || "unknown path"})`)
|
|
363
467
|
);
|
|
364
468
|
} else {
|
|
365
|
-
console.log(
|
|
469
|
+
console.log(chalk3.dim(" MCP: ") + chalk3.yellow("Not configured"));
|
|
366
470
|
}
|
|
367
471
|
const envResult = detectAgentEnvironment();
|
|
368
472
|
if (envResult.detected.length > 0) {
|
|
369
473
|
console.log(
|
|
370
|
-
|
|
474
|
+
chalk3.dim(" Environments: ") + envResult.detected.map((e) => e.name).join(", ")
|
|
371
475
|
);
|
|
372
476
|
}
|
|
373
477
|
console.log("");
|
|
374
478
|
}
|
|
479
|
+
async function showOutcomeStatus(apiKey, outcomeId, json) {
|
|
480
|
+
if (!apiKey) {
|
|
481
|
+
console.log(
|
|
482
|
+
chalk3.red("\n No API key configured. Run `humanlypossible init` first.\n")
|
|
483
|
+
);
|
|
484
|
+
process.exit(1);
|
|
485
|
+
}
|
|
486
|
+
debugLog(`Fetching status for outcome: ${outcomeId}`);
|
|
487
|
+
const statusResult = await getOutcomeStatus(apiKey, outcomeId);
|
|
488
|
+
if (!statusResult.ok || !statusResult.data) {
|
|
489
|
+
debugError("Failed to get status", statusResult.error);
|
|
490
|
+
console.log(chalk3.red(`
|
|
491
|
+
Failed to get status: ${statusResult.error}
|
|
492
|
+
`));
|
|
493
|
+
process.exit(1);
|
|
494
|
+
}
|
|
495
|
+
const status = statusResult.data;
|
|
496
|
+
const terminalStates = ["completed", "failed", "expired", "disputed"];
|
|
497
|
+
let result = null;
|
|
498
|
+
if (terminalStates.includes(status.state)) {
|
|
499
|
+
const resultResponse = await getOutcomeResult(apiKey, outcomeId);
|
|
500
|
+
if (resultResponse.ok) {
|
|
501
|
+
result = resultResponse.data;
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
if (json) {
|
|
505
|
+
console.log(JSON.stringify({ status, result }, null, 2));
|
|
506
|
+
return;
|
|
507
|
+
}
|
|
508
|
+
console.log("");
|
|
509
|
+
console.log(chalk3.bold(` Outcome ${outcomeId.slice(0, 8)}...`));
|
|
510
|
+
console.log("");
|
|
511
|
+
const stateColors = {
|
|
512
|
+
queued: chalk3.gray,
|
|
513
|
+
matching: chalk3.cyan,
|
|
514
|
+
running: chalk3.blue,
|
|
515
|
+
needs_input: chalk3.yellow,
|
|
516
|
+
under_review: chalk3.magenta,
|
|
517
|
+
completed: chalk3.green,
|
|
518
|
+
failed: chalk3.red,
|
|
519
|
+
expired: chalk3.red,
|
|
520
|
+
disputed: chalk3.yellow
|
|
521
|
+
};
|
|
522
|
+
const colorFn = stateColors[status.state] || chalk3.white;
|
|
523
|
+
console.log(chalk3.dim(" State: ") + colorFn(status.state));
|
|
524
|
+
if (status.message) {
|
|
525
|
+
console.log(chalk3.dim(" Message: ") + status.message);
|
|
526
|
+
}
|
|
527
|
+
if (status.progress?.pct !== void 0) {
|
|
528
|
+
console.log(chalk3.dim(" Progress: ") + `${status.progress.pct}%`);
|
|
529
|
+
}
|
|
530
|
+
if (status.progress?.checkpoints) {
|
|
531
|
+
console.log(chalk3.dim(" Checkpoints:"));
|
|
532
|
+
for (const cp of status.progress.checkpoints) {
|
|
533
|
+
console.log(chalk3.white(` - ${cp}`));
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
console.log(chalk3.dim(" Updated: ") + status.updated_at);
|
|
537
|
+
if (result) {
|
|
538
|
+
console.log("");
|
|
539
|
+
console.log(chalk3.dim(" Resolution:"));
|
|
540
|
+
console.log(
|
|
541
|
+
chalk3.dim(" Accepted: ") + (result.resolution.accepted ? chalk3.green("yes") : chalk3.red("no"))
|
|
542
|
+
);
|
|
543
|
+
if (result.resolution.reason) {
|
|
544
|
+
console.log(chalk3.dim(" Reason: ") + result.resolution.reason);
|
|
545
|
+
}
|
|
546
|
+
console.log(chalk3.dim(" Oracle: ") + result.resolution.oracle);
|
|
547
|
+
console.log(
|
|
548
|
+
chalk3.dim(" Verification: ") + result.receipt.verification_level
|
|
549
|
+
);
|
|
550
|
+
if (result.artifacts.length > 0) {
|
|
551
|
+
console.log("");
|
|
552
|
+
console.log(chalk3.dim(" Artifacts:"));
|
|
553
|
+
for (const artifact of result.artifacts) {
|
|
554
|
+
const name = artifact.name || artifact.kind;
|
|
555
|
+
console.log(chalk3.white(` - ${name}`));
|
|
556
|
+
if (artifact.url) {
|
|
557
|
+
console.log(chalk3.dim(` ${artifact.url}`));
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
console.log("");
|
|
563
|
+
}
|
|
375
564
|
|
|
376
565
|
// src/commands/config.ts
|
|
377
|
-
import
|
|
566
|
+
import chalk4 from "chalk";
|
|
378
567
|
async function configCommand(options) {
|
|
379
568
|
if (options.reset) {
|
|
380
569
|
resetConfig();
|
|
381
|
-
console.log(
|
|
570
|
+
console.log(chalk4.green("\n \u2713 Configuration reset\n"));
|
|
382
571
|
return;
|
|
383
572
|
}
|
|
384
573
|
if (options.apiKey) {
|
|
385
574
|
setApiKey(options.apiKey);
|
|
386
|
-
console.log(
|
|
575
|
+
console.log(chalk4.green("\n \u2713 API key updated\n"));
|
|
387
576
|
return;
|
|
388
577
|
}
|
|
389
578
|
const config2 = getConfig();
|
|
390
579
|
console.log("");
|
|
391
|
-
console.log(
|
|
392
|
-
console.log(
|
|
580
|
+
console.log(chalk4.bold(" Configuration"));
|
|
581
|
+
console.log(chalk4.dim(" Path: ") + getConfigPath());
|
|
393
582
|
console.log("");
|
|
394
583
|
console.log(
|
|
395
|
-
|
|
584
|
+
chalk4.dim(" apiKey: ") + (config2.apiKey ? config2.apiKey.slice(0, 4) + "..." + config2.apiKey.slice(-4) : chalk4.red("not set"))
|
|
396
585
|
);
|
|
397
|
-
console.log(
|
|
586
|
+
console.log(chalk4.dim(" apiUrl: ") + config2.apiUrl);
|
|
398
587
|
console.log(
|
|
399
|
-
|
|
588
|
+
chalk4.dim(" mcpConfigured: ") + (config2.mcpConfigured ? chalk4.green("yes") : chalk4.yellow("no"))
|
|
400
589
|
);
|
|
401
590
|
if (config2.mcpConfigPath) {
|
|
402
|
-
console.log(
|
|
591
|
+
console.log(chalk4.dim(" mcpConfigPath: ") + config2.mcpConfigPath);
|
|
403
592
|
}
|
|
404
593
|
console.log("");
|
|
405
594
|
}
|
|
406
595
|
|
|
596
|
+
// src/commands/request.ts
|
|
597
|
+
import prompts2 from "prompts";
|
|
598
|
+
import chalk5 from "chalk";
|
|
599
|
+
var TERMINAL_STATES = ["completed", "failed", "expired", "disputed"];
|
|
600
|
+
var POLL_INTERVAL_MS = 2e3;
|
|
601
|
+
var MAX_POLL_TIME_MS = 3e5;
|
|
602
|
+
async function requestCommand(options) {
|
|
603
|
+
const config2 = getConfig();
|
|
604
|
+
if (!config2.apiKey) {
|
|
605
|
+
console.log(
|
|
606
|
+
chalk5.red("\n No API key configured. Run `humanlypossible init` first.\n")
|
|
607
|
+
);
|
|
608
|
+
process.exit(1);
|
|
609
|
+
}
|
|
610
|
+
let outcome = options.outcome;
|
|
611
|
+
if (!outcome) {
|
|
612
|
+
const response = await prompts2({
|
|
613
|
+
type: "text",
|
|
614
|
+
name: "outcome",
|
|
615
|
+
message: "Describe the outcome you want:",
|
|
616
|
+
validate: (v) => v.length > 0 ? true : "Outcome is required"
|
|
617
|
+
});
|
|
618
|
+
outcome = response.outcome;
|
|
619
|
+
if (!outcome) {
|
|
620
|
+
console.log(chalk5.red("\n Request cancelled.\n"));
|
|
621
|
+
process.exit(1);
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
let resolveBy = options.deadline;
|
|
625
|
+
if (!resolveBy) {
|
|
626
|
+
const response = await prompts2({
|
|
627
|
+
type: "text",
|
|
628
|
+
name: "deadline",
|
|
629
|
+
message: "Deadline (e.g., '1h', '30m', or ISO 8601):",
|
|
630
|
+
initial: "1h"
|
|
631
|
+
});
|
|
632
|
+
resolveBy = response.deadline || "1h";
|
|
633
|
+
}
|
|
634
|
+
const deadline = parseDeadline(resolveBy);
|
|
635
|
+
if (!deadline) {
|
|
636
|
+
console.log(chalk5.red(`
|
|
637
|
+
Invalid deadline format: ${resolveBy}
|
|
638
|
+
`));
|
|
639
|
+
process.exit(1);
|
|
640
|
+
}
|
|
641
|
+
debugLog("Creating outcome request", { outcome, resolve_by: deadline });
|
|
642
|
+
const request = {
|
|
643
|
+
outcome,
|
|
644
|
+
resolve_by: deadline
|
|
645
|
+
};
|
|
646
|
+
console.log("");
|
|
647
|
+
console.log(chalk5.cyan(" Creating outcome request..."));
|
|
648
|
+
const createResult = await createOutcomeRequest(config2.apiKey, request);
|
|
649
|
+
if (!createResult.ok || !createResult.data) {
|
|
650
|
+
debugError("Failed to create outcome", createResult.error);
|
|
651
|
+
console.log(chalk5.red(`
|
|
652
|
+
Failed to create outcome: ${createResult.error}
|
|
653
|
+
`));
|
|
654
|
+
process.exit(1);
|
|
655
|
+
}
|
|
656
|
+
const outcomeId = createResult.data.id;
|
|
657
|
+
console.log(chalk5.green(` Created outcome: ${outcomeId}`));
|
|
658
|
+
if (options.poll === false) {
|
|
659
|
+
if (options.json) {
|
|
660
|
+
console.log(JSON.stringify({ id: outcomeId }));
|
|
661
|
+
} else {
|
|
662
|
+
console.log("");
|
|
663
|
+
console.log(chalk5.dim(" Poll status with:"));
|
|
664
|
+
console.log(chalk5.white(` humanlypossible status --id ${outcomeId}`));
|
|
665
|
+
console.log("");
|
|
666
|
+
}
|
|
667
|
+
return;
|
|
668
|
+
}
|
|
669
|
+
console.log(chalk5.dim(" Waiting for result (Ctrl+C to stop)..."));
|
|
670
|
+
console.log("");
|
|
671
|
+
const startTime = Date.now();
|
|
672
|
+
let lastState = "";
|
|
673
|
+
while (Date.now() - startTime < MAX_POLL_TIME_MS) {
|
|
674
|
+
const statusResult = await getOutcomeStatus(config2.apiKey, outcomeId);
|
|
675
|
+
if (!statusResult.ok || !statusResult.data) {
|
|
676
|
+
debugError("Failed to get status", statusResult.error);
|
|
677
|
+
await sleep(POLL_INTERVAL_MS);
|
|
678
|
+
continue;
|
|
679
|
+
}
|
|
680
|
+
const status = statusResult.data;
|
|
681
|
+
if (status.state !== lastState) {
|
|
682
|
+
lastState = status.state;
|
|
683
|
+
printStatusUpdate(status);
|
|
684
|
+
}
|
|
685
|
+
if (TERMINAL_STATES.includes(status.state)) {
|
|
686
|
+
const resultResponse = await getOutcomeResult(config2.apiKey, outcomeId);
|
|
687
|
+
if (options.json) {
|
|
688
|
+
console.log(
|
|
689
|
+
JSON.stringify({
|
|
690
|
+
id: outcomeId,
|
|
691
|
+
status,
|
|
692
|
+
result: resultResponse.data || null
|
|
693
|
+
})
|
|
694
|
+
);
|
|
695
|
+
} else {
|
|
696
|
+
printFinalResult(status, resultResponse.data);
|
|
697
|
+
}
|
|
698
|
+
return;
|
|
699
|
+
}
|
|
700
|
+
await sleep(POLL_INTERVAL_MS);
|
|
701
|
+
}
|
|
702
|
+
console.log(chalk5.yellow("\n Polling timed out after 5 minutes."));
|
|
703
|
+
console.log(chalk5.dim(` Check status later with: humanlypossible status --id ${outcomeId}
|
|
704
|
+
`));
|
|
705
|
+
}
|
|
706
|
+
function parseDeadline(input) {
|
|
707
|
+
const isoDate = new Date(input);
|
|
708
|
+
if (!isNaN(isoDate.getTime())) {
|
|
709
|
+
return isoDate.toISOString();
|
|
710
|
+
}
|
|
711
|
+
const match = input.match(/^(\d+)\s*(s|m|h|d|w)$/i);
|
|
712
|
+
if (match) {
|
|
713
|
+
const value = parseInt(match[1], 10);
|
|
714
|
+
const unit = match[2].toLowerCase();
|
|
715
|
+
const multipliers = {
|
|
716
|
+
s: 1e3,
|
|
717
|
+
m: 60 * 1e3,
|
|
718
|
+
h: 60 * 60 * 1e3,
|
|
719
|
+
d: 24 * 60 * 60 * 1e3,
|
|
720
|
+
w: 7 * 24 * 60 * 60 * 1e3
|
|
721
|
+
};
|
|
722
|
+
const futureDate = new Date(Date.now() + value * multipliers[unit]);
|
|
723
|
+
return futureDate.toISOString();
|
|
724
|
+
}
|
|
725
|
+
return null;
|
|
726
|
+
}
|
|
727
|
+
function printStatusUpdate(status) {
|
|
728
|
+
const stateColors = {
|
|
729
|
+
queued: chalk5.gray,
|
|
730
|
+
matching: chalk5.cyan,
|
|
731
|
+
running: chalk5.blue,
|
|
732
|
+
needs_input: chalk5.yellow,
|
|
733
|
+
under_review: chalk5.magenta,
|
|
734
|
+
completed: chalk5.green,
|
|
735
|
+
failed: chalk5.red,
|
|
736
|
+
expired: chalk5.red,
|
|
737
|
+
disputed: chalk5.yellow
|
|
738
|
+
};
|
|
739
|
+
const colorFn = stateColors[status.state] || chalk5.white;
|
|
740
|
+
const stateStr = colorFn(`[${status.state}]`);
|
|
741
|
+
const message = status.message ? chalk5.dim(` ${status.message}`) : "";
|
|
742
|
+
const progress = status.progress?.pct !== void 0 ? chalk5.dim(` (${status.progress.pct}%)`) : "";
|
|
743
|
+
console.log(` ${stateStr}${message}${progress}`);
|
|
744
|
+
}
|
|
745
|
+
function printFinalResult(status, result) {
|
|
746
|
+
console.log("");
|
|
747
|
+
if (status.state === "completed" && result) {
|
|
748
|
+
console.log(chalk5.green.bold(" Outcome completed"));
|
|
749
|
+
if (result.resolution.reason) {
|
|
750
|
+
console.log(chalk5.dim(` Reason: ${result.resolution.reason}`));
|
|
751
|
+
}
|
|
752
|
+
if (result.artifacts.length > 0) {
|
|
753
|
+
console.log(chalk5.dim(" Artifacts:"));
|
|
754
|
+
for (const artifact of result.artifacts) {
|
|
755
|
+
const name = artifact.name || artifact.kind;
|
|
756
|
+
const preview = artifact.text_preview ? ` - ${artifact.text_preview.slice(0, 50)}...` : "";
|
|
757
|
+
console.log(chalk5.white(` - ${name}${preview}`));
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
console.log(
|
|
761
|
+
chalk5.dim(` Verification: ${result.receipt.verification_level}`)
|
|
762
|
+
);
|
|
763
|
+
} else if (status.state === "failed") {
|
|
764
|
+
console.log(chalk5.red.bold(" Outcome failed"));
|
|
765
|
+
if (result?.resolution.reason) {
|
|
766
|
+
console.log(chalk5.dim(` Reason: ${result.resolution.reason}`));
|
|
767
|
+
}
|
|
768
|
+
if (result?.resolution.failures) {
|
|
769
|
+
for (const failure of result.resolution.failures) {
|
|
770
|
+
console.log(chalk5.red(` - ${failure}`));
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
} else if (status.state === "expired") {
|
|
774
|
+
console.log(chalk5.red.bold(" Outcome expired"));
|
|
775
|
+
console.log(chalk5.dim(" The deadline passed before completion."));
|
|
776
|
+
} else if (status.state === "disputed") {
|
|
777
|
+
console.log(chalk5.yellow.bold(" Outcome disputed"));
|
|
778
|
+
console.log(chalk5.dim(" A dispute has been filed for this outcome."));
|
|
779
|
+
}
|
|
780
|
+
console.log("");
|
|
781
|
+
}
|
|
782
|
+
function sleep(ms) {
|
|
783
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
// src/commands/serve.ts
|
|
787
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
|
|
788
|
+
import chalk6 from "chalk";
|
|
789
|
+
var DEFAULT_CONFIG = {
|
|
790
|
+
capabilities: [],
|
|
791
|
+
autoAccept: false,
|
|
792
|
+
handler: "manual",
|
|
793
|
+
maxConcurrent: 1
|
|
794
|
+
};
|
|
795
|
+
async function serveCommand(options) {
|
|
796
|
+
const cliConfig = getConfig();
|
|
797
|
+
if (!cliConfig.apiKey) {
|
|
798
|
+
console.log(
|
|
799
|
+
chalk6.red("\n No API key configured. Run `humanlypossible init` first.\n")
|
|
800
|
+
);
|
|
801
|
+
process.exit(1);
|
|
802
|
+
}
|
|
803
|
+
const executorConfig = loadExecutorConfig(options.config);
|
|
804
|
+
const pollInterval = parseInt(options.interval || "10", 10) * 1e3;
|
|
805
|
+
console.log("");
|
|
806
|
+
console.log(chalk6.bold(" HumanlyPossible Executor"));
|
|
807
|
+
console.log(chalk6.dim(" Polling for available outcomes..."));
|
|
808
|
+
console.log("");
|
|
809
|
+
if (executorConfig.capabilities && executorConfig.capabilities.length > 0) {
|
|
810
|
+
console.log(chalk6.dim(" Capabilities:"));
|
|
811
|
+
for (const cap of executorConfig.capabilities) {
|
|
812
|
+
console.log(chalk6.white(` - ${cap}`));
|
|
813
|
+
}
|
|
814
|
+
console.log("");
|
|
815
|
+
}
|
|
816
|
+
console.log(chalk6.dim(` Poll interval: ${pollInterval / 1e3}s`));
|
|
817
|
+
console.log(chalk6.dim(` Handler: ${executorConfig.handler}`));
|
|
818
|
+
console.log(chalk6.dim(" Press Ctrl+C to stop"));
|
|
819
|
+
console.log("");
|
|
820
|
+
const activeClaims = /* @__PURE__ */ new Set();
|
|
821
|
+
let running = true;
|
|
822
|
+
process.on("SIGINT", () => {
|
|
823
|
+
console.log(chalk6.dim("\n Shutting down..."));
|
|
824
|
+
running = false;
|
|
825
|
+
});
|
|
826
|
+
process.on("SIGTERM", () => {
|
|
827
|
+
running = false;
|
|
828
|
+
});
|
|
829
|
+
while (running) {
|
|
830
|
+
try {
|
|
831
|
+
await pollAndProcess(
|
|
832
|
+
cliConfig.apiKey,
|
|
833
|
+
executorConfig,
|
|
834
|
+
activeClaims
|
|
835
|
+
);
|
|
836
|
+
} catch (err) {
|
|
837
|
+
debugError("Polling error", err);
|
|
838
|
+
console.log(chalk6.red(` Error: ${err instanceof Error ? err.message : "Unknown error"}`));
|
|
839
|
+
}
|
|
840
|
+
if (running) {
|
|
841
|
+
await sleep2(pollInterval);
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
console.log(chalk6.green("\n Executor stopped.\n"));
|
|
845
|
+
}
|
|
846
|
+
function loadExecutorConfig(configPath) {
|
|
847
|
+
if (!configPath) {
|
|
848
|
+
const defaultPaths = [
|
|
849
|
+
"humanlypossible.executor.json",
|
|
850
|
+
".humanlypossible/executor.json"
|
|
851
|
+
];
|
|
852
|
+
for (const path of defaultPaths) {
|
|
853
|
+
if (existsSync2(path)) {
|
|
854
|
+
configPath = path;
|
|
855
|
+
break;
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
if (!configPath || !existsSync2(configPath)) {
|
|
860
|
+
debugLog("No executor config found, using defaults");
|
|
861
|
+
return DEFAULT_CONFIG;
|
|
862
|
+
}
|
|
863
|
+
try {
|
|
864
|
+
const content = readFileSync2(configPath, "utf-8");
|
|
865
|
+
const parsed = JSON.parse(content);
|
|
866
|
+
debugLog("Loaded executor config", parsed);
|
|
867
|
+
return { ...DEFAULT_CONFIG, ...parsed };
|
|
868
|
+
} catch (err) {
|
|
869
|
+
debugError("Failed to parse executor config", err);
|
|
870
|
+
console.log(chalk6.yellow(` Warning: Could not parse config at ${configPath}`));
|
|
871
|
+
return DEFAULT_CONFIG;
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
async function pollAndProcess(apiKey, config2, activeClaims) {
|
|
875
|
+
debugLog("Polling for available outcomes");
|
|
876
|
+
const result = await listAvailableOutcomes(apiKey, { limit: 10 });
|
|
877
|
+
if (!result.ok || !result.data) {
|
|
878
|
+
if (result.status === 403) {
|
|
879
|
+
console.log(chalk6.red(" Your API key does not have executor permissions."));
|
|
880
|
+
console.log(chalk6.dim(" Enable 'can_execute' on your actor to use serve mode."));
|
|
881
|
+
process.exit(1);
|
|
882
|
+
}
|
|
883
|
+
debugError("Failed to list available outcomes", result.error);
|
|
884
|
+
return;
|
|
885
|
+
}
|
|
886
|
+
const outcomes = result.data.items;
|
|
887
|
+
if (outcomes.length === 0) {
|
|
888
|
+
debugLog("No available outcomes");
|
|
889
|
+
return;
|
|
890
|
+
}
|
|
891
|
+
debugLog(`Found ${outcomes.length} available outcome(s)`);
|
|
892
|
+
const matchingOutcomes = filterByCapabilities(outcomes, config2.capabilities);
|
|
893
|
+
if (matchingOutcomes.length === 0) {
|
|
894
|
+
debugLog("No outcomes match configured capabilities");
|
|
895
|
+
return;
|
|
896
|
+
}
|
|
897
|
+
const maxConcurrent = config2.maxConcurrent || 1;
|
|
898
|
+
if (activeClaims.size >= maxConcurrent) {
|
|
899
|
+
debugLog(`At max concurrent (${activeClaims.size}/${maxConcurrent})`);
|
|
900
|
+
return;
|
|
901
|
+
}
|
|
902
|
+
const outcome = matchingOutcomes[0];
|
|
903
|
+
if (activeClaims.has(outcome.id)) {
|
|
904
|
+
debugLog(`Already processing ${outcome.id}`);
|
|
905
|
+
return;
|
|
906
|
+
}
|
|
907
|
+
console.log("");
|
|
908
|
+
printOutcomeSummary(outcome);
|
|
909
|
+
if (config2.autoAccept) {
|
|
910
|
+
await processOutcome(apiKey, outcome, config2, activeClaims);
|
|
911
|
+
} else {
|
|
912
|
+
console.log(chalk6.dim(" (Auto-accept disabled. Configure autoAccept: true to claim automatically.)"));
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
function filterByCapabilities(outcomes, capabilities) {
|
|
916
|
+
if (!capabilities || capabilities.length === 0) {
|
|
917
|
+
return outcomes;
|
|
918
|
+
}
|
|
919
|
+
return outcomes.filter((outcome) => {
|
|
920
|
+
if (!outcome.outcome) return false;
|
|
921
|
+
const outcomeText = outcome.outcome.toLowerCase();
|
|
922
|
+
return capabilities.some((cap) => {
|
|
923
|
+
const capLower = cap.toLowerCase();
|
|
924
|
+
return outcomeText.includes(capLower);
|
|
925
|
+
});
|
|
926
|
+
});
|
|
927
|
+
}
|
|
928
|
+
function printOutcomeSummary(outcome) {
|
|
929
|
+
console.log(chalk6.cyan(` New outcome available: ${outcome.id.slice(0, 8)}...`));
|
|
930
|
+
console.log(chalk6.white(` "${outcome.outcome || "No description"}"`));
|
|
931
|
+
if (outcome.budget?.max_usd) {
|
|
932
|
+
console.log(chalk6.dim(` Budget: $${outcome.budget.max_usd}`));
|
|
933
|
+
}
|
|
934
|
+
if (outcome.urgency?.priority) {
|
|
935
|
+
console.log(chalk6.dim(` Priority: ${outcome.urgency.priority}`));
|
|
936
|
+
}
|
|
937
|
+
if (outcome.constraints?.location) {
|
|
938
|
+
console.log(chalk6.dim(` Location: ${outcome.constraints.location}`));
|
|
939
|
+
}
|
|
940
|
+
const deadline = new Date(outcome.resolve_by);
|
|
941
|
+
const now = /* @__PURE__ */ new Date();
|
|
942
|
+
const hoursRemaining = Math.round(
|
|
943
|
+
(deadline.getTime() - now.getTime()) / (1e3 * 60 * 60)
|
|
944
|
+
);
|
|
945
|
+
console.log(chalk6.dim(` Deadline: ${hoursRemaining}h remaining`));
|
|
946
|
+
}
|
|
947
|
+
async function processOutcome(apiKey, outcome, config2, activeClaims) {
|
|
948
|
+
console.log(chalk6.cyan(` Claiming outcome...`));
|
|
949
|
+
activeClaims.add(outcome.id);
|
|
950
|
+
const claimResult = await claimOutcome(apiKey, outcome.id);
|
|
951
|
+
if (!claimResult.ok) {
|
|
952
|
+
console.log(chalk6.red(` Failed to claim: ${claimResult.error}`));
|
|
953
|
+
activeClaims.delete(outcome.id);
|
|
954
|
+
return;
|
|
955
|
+
}
|
|
956
|
+
console.log(chalk6.green(` Claimed successfully!`));
|
|
957
|
+
if (config2.handler === "auto") {
|
|
958
|
+
console.log(chalk6.yellow(` Auto-handler not yet implemented.`));
|
|
959
|
+
console.log(chalk6.dim(` Mark complete manually or implement a script handler.`));
|
|
960
|
+
} else if (config2.handler === "script" && config2.scriptPath) {
|
|
961
|
+
await runScriptHandler(apiKey, outcome, config2.scriptPath, activeClaims);
|
|
962
|
+
} else {
|
|
963
|
+
console.log(chalk6.dim(` Waiting for manual completion...`));
|
|
964
|
+
console.log(chalk6.dim(` Submit via: POST /v0/outcomes/${outcome.id}/submit`));
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
async function runScriptHandler(apiKey, outcome, scriptPath, activeClaims) {
|
|
968
|
+
if (!existsSync2(scriptPath)) {
|
|
969
|
+
console.log(chalk6.red(` Script not found: ${scriptPath}`));
|
|
970
|
+
activeClaims.delete(outcome.id);
|
|
971
|
+
return;
|
|
972
|
+
}
|
|
973
|
+
console.log(chalk6.dim(` Running script: ${scriptPath}`));
|
|
974
|
+
try {
|
|
975
|
+
const scriptModule = await import(scriptPath);
|
|
976
|
+
const handler = scriptModule.default || scriptModule.handle;
|
|
977
|
+
if (typeof handler !== "function") {
|
|
978
|
+
console.log(chalk6.red(` Script must export a default handler function`));
|
|
979
|
+
activeClaims.delete(outcome.id);
|
|
980
|
+
return;
|
|
981
|
+
}
|
|
982
|
+
const result = await handler(outcome);
|
|
983
|
+
if (result && result.resolution) {
|
|
984
|
+
const payload = {
|
|
985
|
+
result: {
|
|
986
|
+
resolution: {
|
|
987
|
+
accepted: result.resolution.accepted ?? true,
|
|
988
|
+
decided_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
989
|
+
oracle: result.resolution.oracle ?? "executor",
|
|
990
|
+
reason: result.resolution.reason
|
|
991
|
+
},
|
|
992
|
+
artifacts: result.artifacts ?? [],
|
|
993
|
+
receipt: {
|
|
994
|
+
verification_level: result.receipt?.verification_level ?? "L0"
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
};
|
|
998
|
+
const submitResult = await submitOutcome(apiKey, outcome.id, payload);
|
|
999
|
+
if (submitResult.ok) {
|
|
1000
|
+
console.log(chalk6.green(` Outcome completed!`));
|
|
1001
|
+
} else {
|
|
1002
|
+
console.log(chalk6.red(` Failed to submit: ${submitResult.error}`));
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
} catch (err) {
|
|
1006
|
+
debugError("Script handler error", err);
|
|
1007
|
+
console.log(chalk6.red(` Script error: ${err instanceof Error ? err.message : "Unknown"}`));
|
|
1008
|
+
} finally {
|
|
1009
|
+
activeClaims.delete(outcome.id);
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
function sleep2(ms) {
|
|
1013
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
1014
|
+
}
|
|
1015
|
+
|
|
407
1016
|
// src/cli.ts
|
|
408
1017
|
var program = new Command();
|
|
409
|
-
program.name("humanlypossible").description("Connect AI agents to humans in the real world").version("0.1.0")
|
|
1018
|
+
program.name("humanlypossible").description("Connect AI agents to humans in the real world").version("0.1.0").option("--debug", "Enable verbose debug logging").hook("preAction", (thisCommand) => {
|
|
1019
|
+
const opts = thisCommand.opts();
|
|
1020
|
+
if (opts.debug) {
|
|
1021
|
+
setDebug(true);
|
|
1022
|
+
}
|
|
1023
|
+
});
|
|
410
1024
|
program.command("init").description("Set up HumanlyPossible in your project").option("--api-key <key>", "Provide API key directly (skips interactive prompt)").option("--no-mcp", "Skip MCP server configuration").action(initCommand);
|
|
411
|
-
program.command("status").description("Check your HumanlyPossible connection and configuration").action(statusCommand);
|
|
1025
|
+
program.command("status").description("Check your HumanlyPossible connection and configuration").option("--id <outcomeId>", "Show status of a specific outcome").option("--json", "Output as JSON").action(statusCommand);
|
|
412
1026
|
program.command("config").description("View or update your configuration").option("--api-key <key>", "Update your API key").option("--reset", "Reset all configuration").action(configCommand);
|
|
1027
|
+
program.command("request").description("Create an outcome request and poll for result").option("-o, --outcome <description>", "Outcome description").option("-d, --deadline <datetime>", "Deadline (ISO 8601 or relative like '1h', '30m')").option("--json", "Output result as JSON").option("--no-poll", "Create request but do not poll for result").action(requestCommand);
|
|
1028
|
+
program.command("serve").description("Run as an executor: poll for outcomes and fulfill them").option("-c, --config <path>", "Path to executor config file").option("-i, --interval <seconds>", "Polling interval in seconds", "10").action(serveCommand);
|
|
413
1029
|
program.parse();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "humanlypossible",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "CLI for HumanlyPossible - connect AI agents to humans in the real world",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -26,7 +26,8 @@
|
|
|
26
26
|
"node": ">=20"
|
|
27
27
|
},
|
|
28
28
|
"files": [
|
|
29
|
-
"dist"
|
|
29
|
+
"dist",
|
|
30
|
+
"README.md"
|
|
30
31
|
],
|
|
31
32
|
"keywords": [
|
|
32
33
|
"humanlypossible",
|