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.
Files changed (3) hide show
  1. package/README.md +40 -0
  2. package/dist/cli.js +695 -79
  3. 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 chalk from "chalk";
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 validateApiKey(apiKey) {
85
+ async function apiRequest(method, path, apiKey, body) {
86
+ const url = `${getApiUrl()}${path}`;
87
+ debugLog(`API ${method} ${url}`);
50
88
  try {
51
- const response = await fetch(`${getApiUrl()}/v1/auth/validate`, {
52
- method: "POST",
53
- headers: {
54
- Authorization: `Bearer ${apiKey}`,
55
- "Content-Type": "application/json"
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
- return response.ok;
59
- } catch {
60
- if (apiKey.startsWith("hp_") && apiKey.length > 10) {
61
- return true;
62
- }
63
- return false;
64
- }
65
- }
66
- async function healthCheck(apiKey) {
67
- const start = Date.now();
68
- try {
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
- return { ok: false, latencyMs, message: `HTTP ${response.status}` };
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
- const latencyMs = Date.now() - start;
116
+ debugError("API request failed", err);
81
117
  return {
82
118
  ok: false,
83
- latencyMs,
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(chalk.bold(" Welcome to HumanlyPossible"));
186
- console.log(chalk.dim(" Connect AI agents to humans in the real world"));
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(chalk.red("\n Setup cancelled.\n"));
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
- chalk.dim(
202
- " Check your key at https://humanlypossible.ai/dashboard/settings\n"
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
- chalk.cyan(
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(chalk.green(` \u2713 MCP server configured at ${configPath}`));
315
+ console.log(chalk2.green(` \u2713 MCP server configured at ${configPath}`));
226
316
  }
227
317
  } else {
228
- console.log(chalk.dim("\n No agent environment detected (Claude Desktop, Cursor, etc.)"));
229
- console.log(chalk.dim(" You can configure MCP manually later with: humanlypossible config\n"));
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
- await sleep(1e3);
241
- testSpinner.succeed("Connection verified");
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(chalk.green.bold(" \u2713 HumanlyPossible is ready"));
345
+ console.log(chalk2.green.bold(" \u2713 HumanlyPossible is ready"));
245
346
  console.log("");
246
- console.log(chalk.dim(" Your agent can now use:"));
347
+ console.log(chalk2.dim(" Your agent can now use:"));
247
348
  console.log(
248
- chalk.white(' book_human({ task: "Pick up coffee", location: "..." })')
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(chalk.dim(" Useful commands:"));
252
- console.log(chalk.white(" humanlypossible status ") + chalk.dim("Check your setup"));
253
- console.log(chalk.white(" humanlypossible config ") + chalk.dim("Update settings"));
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(chalk.dim("\n Opening humanlypossible.ai in your browser..."));
298
- await open("https://humanlypossible.ai/dashboard/settings");
400
+ console.log(chalk2.dim("\n Opening humanlypossible.ai in your browser..."));
401
+ await open("https://humanlypossible.ai/dashboard");
299
402
  console.log(
300
- chalk.dim(" Sign up or log in, then copy your API key.\n")
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(chalk.cyan(` \u27F3 ${message}`));
420
+ process.stdout.write(chalk2.cyan(` \u27F3 ${message}`));
318
421
  return {
319
422
  succeed(msg) {
320
- process.stdout.write(`\r${chalk.green(` \u2713 ${msg}`)}
423
+ process.stdout.write(`\r${chalk2.green(` \u2713 ${msg}`)}
321
424
  `);
322
425
  },
323
426
  fail(msg) {
324
- process.stdout.write(`\r${chalk.red(` \u2717 ${msg}`)}
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 chalk2 from "chalk";
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(chalk2.bold(" HumanlyPossible Status"));
442
+ console.log(chalk3.bold(" HumanlyPossible Status"));
339
443
  console.log("");
340
- console.log(chalk2.dim(" Config: ") + getConfigPath());
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(chalk2.dim(" API Key: ") + chalk2.green(masked));
447
+ console.log(chalk3.dim(" API Key: ") + chalk3.green(masked));
344
448
  } else {
345
- console.log(chalk2.dim(" API Key: ") + chalk2.red("Not configured"));
346
- console.log(chalk2.dim(" Run `humanlypossible init` to get started"));
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
- chalk2.dim(" API: ") + chalk2.green(`Connected (${health.latencyMs}ms)`)
457
+ chalk3.dim(" API: ") + chalk3.green(`Connected (${health.latencyMs}ms)`)
354
458
  );
355
459
  } else {
356
460
  console.log(
357
- chalk2.dim(" API: ") + chalk2.red(health.message || "Unreachable")
461
+ chalk3.dim(" API: ") + chalk3.red(health.message || "Unreachable")
358
462
  );
359
463
  }
360
464
  if (config2.mcpConfigured) {
361
465
  console.log(
362
- chalk2.dim(" MCP: ") + chalk2.green(`Configured (${config2.mcpConfigPath || "unknown path"})`)
466
+ chalk3.dim(" MCP: ") + chalk3.green(`Configured (${config2.mcpConfigPath || "unknown path"})`)
363
467
  );
364
468
  } else {
365
- console.log(chalk2.dim(" MCP: ") + chalk2.yellow("Not configured"));
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
- chalk2.dim(" Environments: ") + envResult.detected.map((e) => e.name).join(", ")
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 chalk3 from "chalk";
566
+ import chalk4 from "chalk";
378
567
  async function configCommand(options) {
379
568
  if (options.reset) {
380
569
  resetConfig();
381
- console.log(chalk3.green("\n \u2713 Configuration reset\n"));
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(chalk3.green("\n \u2713 API key updated\n"));
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(chalk3.bold(" Configuration"));
392
- console.log(chalk3.dim(" Path: ") + getConfigPath());
580
+ console.log(chalk4.bold(" Configuration"));
581
+ console.log(chalk4.dim(" Path: ") + getConfigPath());
393
582
  console.log("");
394
583
  console.log(
395
- chalk3.dim(" apiKey: ") + (config2.apiKey ? config2.apiKey.slice(0, 4) + "..." + config2.apiKey.slice(-4) : chalk3.red("not set"))
584
+ chalk4.dim(" apiKey: ") + (config2.apiKey ? config2.apiKey.slice(0, 4) + "..." + config2.apiKey.slice(-4) : chalk4.red("not set"))
396
585
  );
397
- console.log(chalk3.dim(" apiUrl: ") + config2.apiUrl);
586
+ console.log(chalk4.dim(" apiUrl: ") + config2.apiUrl);
398
587
  console.log(
399
- chalk3.dim(" mcpConfigured: ") + (config2.mcpConfigured ? chalk3.green("yes") : chalk3.yellow("no"))
588
+ chalk4.dim(" mcpConfigured: ") + (config2.mcpConfigured ? chalk4.green("yes") : chalk4.yellow("no"))
400
589
  );
401
590
  if (config2.mcpConfigPath) {
402
- console.log(chalk3.dim(" mcpConfigPath: ") + config2.mcpConfigPath);
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.0",
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",