@profullstack/agenticjobs 0.11.0 → 0.14.0

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 (123) hide show
  1. package/README.md +136 -1
  2. package/dist/cli/args.js +4 -2
  3. package/dist/cli/args.js.map +1 -1
  4. package/dist/cli/index.js +227 -5
  5. package/dist/cli/index.js.map +1 -1
  6. package/dist/cli/jobfile.js +38 -2
  7. package/dist/cli/jobfile.js.map +1 -1
  8. package/dist/client/client.d.ts +157 -0
  9. package/dist/client/client.js +44 -0
  10. package/dist/client/client.js.map +1 -1
  11. package/dist/config.d.ts +35 -1
  12. package/dist/config.js +33 -1
  13. package/dist/config.js.map +1 -1
  14. package/dist/core/agentwriter.js +22 -5
  15. package/dist/core/agentwriter.js.map +1 -1
  16. package/dist/core/applications.js +1 -1
  17. package/dist/core/applications.js.map +1 -1
  18. package/dist/core/candidates.js +3 -5
  19. package/dist/core/candidates.js.map +1 -1
  20. package/dist/core/capacity.d.ts +1 -1
  21. package/dist/core/capacity.js +11 -19
  22. package/dist/core/capacity.js.map +1 -1
  23. package/dist/core/coinpay.d.ts +143 -0
  24. package/dist/core/coinpay.js +417 -0
  25. package/dist/core/coinpay.js.map +1 -0
  26. package/dist/core/import.js +19 -9
  27. package/dist/core/import.js.map +1 -1
  28. package/dist/core/inbox.d.ts +155 -0
  29. package/dist/core/inbox.js +353 -0
  30. package/dist/core/inbox.js.map +1 -0
  31. package/dist/core/invoices.d.ts +111 -0
  32. package/dist/core/invoices.js +292 -0
  33. package/dist/core/invoices.js.map +1 -0
  34. package/dist/core/jobs.d.ts +24 -6
  35. package/dist/core/jobs.js +116 -31
  36. package/dist/core/jobs.js.map +1 -1
  37. package/dist/core/mail.d.ts +16 -0
  38. package/dist/core/mail.js +22 -0
  39. package/dist/core/mail.js.map +1 -1
  40. package/dist/core/recommendations.d.ts +134 -0
  41. package/dist/core/recommendations.js +356 -0
  42. package/dist/core/recommendations.js.map +1 -0
  43. package/dist/core/updates.d.ts +15 -3
  44. package/dist/core/updates.js +58 -24
  45. package/dist/core/updates.js.map +1 -1
  46. package/dist/core/zip.js +29 -4
  47. package/dist/core/zip.js.map +1 -1
  48. package/dist/directory/federate.js +3 -1
  49. package/dist/directory/federate.js.map +1 -1
  50. package/dist/directory/fetch.d.ts +1 -1
  51. package/dist/markup/markdown.js +6 -2
  52. package/dist/markup/markdown.js.map +1 -1
  53. package/dist/markup/resume.js +18 -5
  54. package/dist/markup/resume.js.map +1 -1
  55. package/dist/mcp/tools.js +249 -7
  56. package/dist/mcp/tools.js.map +1 -1
  57. package/dist/schema/index.d.ts +1 -0
  58. package/dist/schema/index.js +1 -0
  59. package/dist/schema/index.js.map +1 -1
  60. package/dist/schema/instance.js +29 -0
  61. package/dist/schema/instance.js.map +1 -1
  62. package/dist/schema/job.d.ts +12 -0
  63. package/dist/schema/job.js +17 -3
  64. package/dist/schema/job.js.map +1 -1
  65. package/dist/schema/jsonld.js +7 -6
  66. package/dist/schema/jsonld.js.map +1 -1
  67. package/dist/schema/pay.d.ts +173 -0
  68. package/dist/schema/pay.js +668 -0
  69. package/dist/schema/pay.js.map +1 -0
  70. package/dist/server/app.d.ts +4 -3
  71. package/dist/server/app.js +10 -5
  72. package/dist/server/app.js.map +1 -1
  73. package/dist/server/deps.d.ts +5 -0
  74. package/dist/server/middleware.d.ts +11 -1
  75. package/dist/server/middleware.js +26 -3
  76. package/dist/server/middleware.js.map +1 -1
  77. package/dist/server/routes/api.js +437 -7
  78. package/dist/server/routes/api.js.map +1 -1
  79. package/dist/server/routes/discovery.js +29 -8
  80. package/dist/server/routes/discovery.js.map +1 -1
  81. package/dist/server/routes/inbox.d.ts +9 -0
  82. package/dist/server/routes/inbox.js +223 -0
  83. package/dist/server/routes/inbox.js.map +1 -0
  84. package/dist/server/routes/openapi.js +260 -2
  85. package/dist/server/routes/openapi.js.map +1 -1
  86. package/dist/server/routes/pages.d.ts +22 -0
  87. package/dist/server/routes/pages.js +244 -19
  88. package/dist/server/routes/pages.js.map +1 -1
  89. package/dist/tui/views.js +9 -3
  90. package/dist/tui/views.js.map +1 -1
  91. package/dist/views/candidates.d.ts +7 -0
  92. package/dist/views/candidates.js +3 -2
  93. package/dist/views/candidates.js.map +1 -1
  94. package/dist/views/docs.js +10 -1
  95. package/dist/views/docs.js.map +1 -1
  96. package/dist/views/inbox.d.ts +90 -0
  97. package/dist/views/inbox.js +73 -0
  98. package/dist/views/inbox.js.map +1 -0
  99. package/dist/views/jobs.d.ts +25 -0
  100. package/dist/views/jobs.js +35 -12
  101. package/dist/views/jobs.js.map +1 -1
  102. package/dist/views/layout.d.ts +24 -0
  103. package/dist/views/layout.js +20 -2
  104. package/dist/views/layout.js.map +1 -1
  105. package/dist/views/me.d.ts +10 -3
  106. package/dist/views/me.js +3 -2
  107. package/dist/views/me.js.map +1 -1
  108. package/dist/views/post.d.ts +18 -0
  109. package/dist/views/post.js +39 -7
  110. package/dist/views/post.js.map +1 -1
  111. package/dist/views/recommendations.d.ts +43 -0
  112. package/dist/views/recommendations.js +26 -0
  113. package/dist/views/recommendations.js.map +1 -0
  114. package/dist/views/updates.d.ts +13 -0
  115. package/dist/views/updates.js +6 -5
  116. package/dist/views/updates.js.map +1 -1
  117. package/docs/openjob.md +63 -0
  118. package/migrations/0014_inbox_and_billing.sql +138 -0
  119. package/migrations/0015_pay_lines.sql +38 -0
  120. package/migrations/0016_recommendations.sql +65 -0
  121. package/package.json +1 -1
  122. package/web/public/app.css +192 -2
  123. package/web/public/sw.js +1 -1
package/README.md CHANGED
@@ -120,6 +120,12 @@ The convention is [OpenResume.md](docs/openresume.md). The listing format is
120
120
  [OpenJob](docs/openjob.md). Both are conventions anyone can implement, and neither
121
121
  requires this software.
122
122
 
123
+ The same rule holds for every other box on the board. A listing description, an
124
+ employer profile, a cover letter, a message, an invoice description, an update and a
125
+ recommendation all take plain text or Markdown, and each is shown rendered: paste an
126
+ email and it reads as written, paste a bullet list and it is a list. Nothing anyone
127
+ types ever becomes a tag.
128
+
123
129
  ## Run your own
124
130
 
125
131
  ```bash
@@ -227,7 +233,8 @@ agenticjobs submit <id> # you decide
227
233
 
228
234
  # hiring, on the same account
229
235
  agenticjobs post job.md --org acme # creates a draft
230
- agenticjobs publish staff-engineer # you decide
236
+ agenticjobs post job.md --org acme --pay "$0.25 per task" --pay-method SOL
237
+ agenticjobs publish staff-engineer # you decide; it has to say what it pays
231
238
  agenticjobs applications staff-engineer
232
239
  agenticjobs decide <id> hired # reviewing, rejected or hired
233
240
  ```
@@ -240,6 +247,68 @@ Sign-in is the device flow: the terminal shows a short code, you approve it in a
240
247
  browser, and no credential crosses the terminal. A terminal token can read and apply
241
248
  and is never an administrator.
242
249
 
250
+ ## Recommendations, not ratings
251
+
252
+ A star out of five from a stranger who paid you once is a number that says nothing
253
+ and cannot be answered; it is what turns a profile into a scoreboard. This board does
254
+ what LinkedIn does instead: a recommendation is a paragraph with a name on it, from an
255
+ employer who hired you or a person you hired, and it goes on your page only when you
256
+ approve it. Reject it and nothing is shown, to anybody. You can take one down later.
257
+
258
+ Both directions, because the board is symmetric: an employer recommends a candidate,
259
+ and a candidate recommends an employer. The author has to have a page, a published
260
+ resume or an employer they post for, so a recommendation from an account made this
261
+ morning is not accepted. One per author per subject; writing again replaces it and
262
+ asks for approval again. Ten a day.
263
+
264
+ ```bash
265
+ agenticjobs recommend ada-lovelace --candidate --as acme \
266
+ --relationship "hired her for a three-month contract" \
267
+ "Shipped the whole thing two weeks early and wrote the docs nobody asked for."
268
+ agenticjobs recommend acme "Paid on time, every time, and the brief was the brief."
269
+ agenticjobs recommendations # waiting for you, about you, by you
270
+ agenticjobs recommendations approve <id>
271
+ ```
272
+
273
+ The same over the API (`/api/v1/candidates/{slug}/recommendations`,
274
+ `/api/v1/orgs/{slug}/recommendations`, `/api/v1/me/recommendations`) and MCP
275
+ (`recommend`, `list_recommendations`, `decide_recommendation`), so an agent can draft
276
+ one for a person to send, and a person's agent can read what is waiting for them.
277
+
278
+ ## What it pays
279
+
280
+ A listing has to say what it pays before it can be published. "Unpaid" counts, because
281
+ it is an answer; silence does not. A listing that says nothing about pay gets fewer and
282
+ worse applications, and an agent reading it cannot tell whether to bother.
283
+
284
+ The vocabulary is [ugig.net](https://ugig.net)'s, because the work this board is for is
285
+ not always a salary: per task, per pull request, per social post, a flat fee, a share of
286
+ revenue, a bounty, in dollars or in a coin, settled in whatever the two sides agree on.
287
+ You write each price the way you would say it, one per line, anywhere a listing is
288
+ written: the form, the API, the job file, the MCP tool.
289
+
290
+ ```
291
+ $120k - $150k a year
292
+ $0.25 per task
293
+ $0.25 per PR that fixes a bug you find
294
+ 0.01 SOL per task
295
+ $5000 fixed
296
+ 10% revenue share
297
+ ```
298
+
299
+ ```yaml
300
+ # in a job file
301
+ pay:
302
+ - $0.25 per task
303
+ - $0.25 per PR that fixes a bug you find
304
+ pay_method: SOL
305
+ ```
306
+
307
+ The settlement method is separate from the price: "$100 an hour paid in USDC" is one
308
+ rate with a preference, not two. It is a coin (`SOL`, `USDC`, `ETH`, `USDT`, `POL`) or a
309
+ rail (`bank transfer`, `PayPal`, `payroll`). The full shape is in
310
+ [the OpenJob spec](docs/openjob.md#pay).
311
+
243
312
  ## Updates
244
313
 
245
314
  Between "posted a job" and silence there is the rest of it: a role filled, something
@@ -271,6 +340,72 @@ author, and the same text twice is refused. 600 characters and one link, rendere
271
340
  text and marked `nofollow`, because a board with an open posting form is a link farm
272
341
  the moment it passes PageRank on.
273
342
 
343
+ ## Inbox, and invoices
344
+
345
+ There is no public comment box anywhere on the board. A board with one gets used as
346
+ the place to hand in invoices, because an invoice has to go somewhere and a comment
347
+ under a listing was the only somewhere. So reaching somebody here is private: a
348
+ conversation between you and a candidate, or you and an employer, and nobody else.
349
+ Writing to an employer reaches every member of it.
350
+
351
+ ```bash
352
+ agenticjobs message acme "Is the Go role open to contractors?" --job senior-go-engineer
353
+ agenticjobs message jane-doe --candidate "Saw your resume. Free for a call this week?"
354
+ agenticjobs inbox # your conversations
355
+ agenticjobs inbox read <id> # one, with its invoices
356
+ agenticjobs reply <id> "Thursday works."
357
+ ```
358
+
359
+ The button is on every candidate's and every employer's page, and on a listing. Twenty
360
+ new conversations a day per account; replies are not counted. The other side is
361
+ emailed that there is a message, never the message itself: the conversation is private
362
+ to the people in it and somebody else's mail server is not.
363
+
364
+ An invoice is a message with money attached. The payee sends it from the conversation,
365
+ in US dollars, choosing a chain they hold a wallet for; the other side presses Pay and
366
+ settles it on [CoinPay](https://coinpayportal.com), straight to that wallet. The board
367
+ never holds the money.
368
+
369
+ ```bash
370
+ agenticjobs billing # is a CoinPay account connected?
371
+ agenticjobs invoice <thread-id> 1200 --currency USDC_POL --for "Sprint 3, as agreed"
372
+ agenticjobs invoices # sent and received
373
+ agenticjobs pay <invoice-id> # a quote, and the page to pay on
374
+ ```
375
+
376
+ Connecting a CoinPay account is a browser step (`/me`, Billing, Connect CoinPay): it is
377
+ CoinPay's consent screen, asking for `wallet:read` and nothing else. The board reads
378
+ which wallets you can be paid to and uses the token for nothing else; it cannot move
379
+ funds or create anything on your account.
380
+
381
+ ### Turning billing on
382
+
383
+ Billing is off until the operator gives the board its own CoinPay credentials. Two
384
+ sets, because two jobs: an OAuth client so *people* can connect their accounts, and a
385
+ business key so the *board* can mint the payment a payer settles. All four or none;
386
+ a partial set is logged at boot and leaves billing off rather than half on.
387
+
388
+ ```bash
389
+ COINPAY_CLIENT_ID=cp_... # coinpay oauth create --name your-board \
390
+ COINPAY_CLIENT_SECRET=cps_... # --redirect-uri https://your-board/api/v1/coinpay/callback \
391
+ # --scope openid,profile,email,wallet:read
392
+ COINPAY_API_KEY=cp_live_... # coinpay business create --name your-board \
393
+ COINPAY_BUSINESS_ID=... # --category marketplace \
394
+ COINPAY_WEBHOOK_SECRET=whsec_... # --webhook-url https://your-board/api/v1/coinpay/webhook
395
+ COINPAY_URL=https://coinpayportal.com # optional
396
+ ```
397
+
398
+ The redirect URI has to be registered on the OAuth client byte for byte, and the
399
+ client has to be registered for `wallet:read`: CoinPay narrows a grant to the client's
400
+ registered scopes without an error, and a board that trusts its own request shows
401
+ "Connected" beside an account it cannot read a wallet from. The board checks the scope
402
+ on the token it got back, and says "Reconnect required" when it is missing.
403
+
404
+ Payment is confirmed by CoinPay's webhook, and by asking CoinPay whenever the
405
+ conversation is opened, so a lost webhook delays the answer rather than losing it.
406
+ Without `COINPAY_WEBHOOK_SECRET` every webhook is refused and polling is the only
407
+ source, which works and is slower.
408
+
274
409
  ## Posting from myna
275
410
 
276
411
  A job opening goes out with the rest of a launch:
package/dist/cli/args.js CHANGED
@@ -100,7 +100,9 @@ export function flagString(args, ...names) {
100
100
  }
101
101
  export function flagBool(args, ...names) {
102
102
  for (const name of names) {
103
- const value = args.flags[name];
103
+ const raw = args.flags[name];
104
+ // Repeated options use their last value, just like flagString.
105
+ const value = Array.isArray(raw) ? raw[raw.length - 1] : raw;
104
106
  if (value === true)
105
107
  return true;
106
108
  if (typeof value === 'string')
@@ -123,7 +125,7 @@ export function flagNumber(args, ...names) {
123
125
  const raw = flagString(args, ...names);
124
126
  if (raw === undefined)
125
127
  return undefined;
126
- const parsed = Number.parseInt(raw, 10);
128
+ const parsed = Number(raw);
127
129
  return Number.isFinite(parsed) ? parsed : undefined;
128
130
  }
129
131
  //# sourceMappingURL=args.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"args.js","sourceRoot":"","sources":["../../src/cli/args.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"args.js","sourceRoot":"","sources":["../../src/cli/args.ts"],"names":[],"mappings":"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"}
package/dist/cli/index.js CHANGED
@@ -18,7 +18,8 @@ import { bold, dim } from "./format.js";
18
18
  import { installLine, update as runUpdate, uninstall as runUninstall, whereIsIt } from "./manage.js";
19
19
  import { ApiError, BoardClient, configuredBoards, currentBoard, DEFAULT_SERVER, forgetBoard, loadConfig, login, LoginError, normaliseServer, rememberBoard, saveConfig, searchEverywhere, } from "../client/index.js";
20
20
  import { VERSION } from "../config.js";
21
- import { ago, formatSalary } from "../schema/text.js";
21
+ import { ago } from "../schema/text.js";
22
+ import { formatMethod, formatPay, formatPayShort, payOfJob } from "../schema/pay.js";
22
23
  import { APPLICATION_DECISIONS, isApplicationDecision } from "../schema/job.js";
23
24
  // The vocabulary, not the storage: core/resumes.ts is pure apart from a type
24
25
  // import of pg, so the CLI can name the same three values the server does
@@ -71,6 +72,27 @@ const USAGE = `agenticjobs ${VERSION} - an agent-friendly job board you can self
71
72
  follow <slug> follow an employer; --candidate for a person
72
73
  unfollow <slug> stop
73
74
 
75
+ Recommendations
76
+ recommend <slug> <text> recommend an employer; --candidate for a person
77
+ --as <employer-slug> ...from an employer you post for
78
+ --relationship <text> "hired them for a three-month contract"
79
+ recommendations about you, and what you wrote
80
+ recommendations approve|reject|withdraw <id>
81
+
82
+ Inbox
83
+ inbox your conversations
84
+ inbox read <id> one conversation, and its invoices
85
+ message <slug> <text> write to an employer; --candidate for a person
86
+ --job <slug> about a listing
87
+ --as <employer-slug> write as an employer you belong to
88
+ --subject <text>
89
+ reply <id> <text> reply in a conversation
90
+ invoice <id> <amount> bill the other side, in USD; --currency <chain>
91
+ --for <text> what it is for
92
+ invoices every invoice you sent or can pay
93
+ pay <invoice-id> get a quote and the CoinPay page to pay on
94
+ billing whether your CoinPay account is connected
95
+
74
96
  Hiring
75
97
  employer list the employers you can post under
76
98
  employer show <slug>
@@ -78,12 +100,16 @@ const USAGE = `agenticjobs ${VERSION} - an agent-friendly job board you can self
78
100
  employer update <slug> [--name n] [--website u] [--description d]
79
101
  employer delete <slug> --yes
80
102
  post <file.md> post a job; stays a draft until you publish
103
+ --pay "<line>" what it pays, repeatable: "$0.25 per task",
104
+ "$120k - $150k a year", "$5000 fixed"
105
+ --pay-method <how> SOL, USDC, bank transfer, PayPal, payroll
106
+ --unpaid the role pays nothing, said out loud
81
107
  new <url> import a job from a URL, as a draft
82
108
  update <url> re-read that URL into the listing it created
83
109
  --slug <slug> ...adopting a listing that was written by hand
84
110
  (with no URL, updates this install instead)
85
111
  edit <slug> <file.md> rewrite a listing, keeping its URL
86
- publish <slug> take a draft live
112
+ publish <slug> take a draft live (it has to say what it pays)
87
113
  close <slug> close a listing
88
114
  applications <slug> what came in
89
115
  decide <id> <status> reviewing, rejected or hired
@@ -275,9 +301,27 @@ async function run(args) {
275
301
  return commandDecide(args);
276
302
  case 'news':
277
303
  return commandNews(args);
304
+ case 'recommend':
305
+ return commandRecommend(args);
306
+ case 'recommendations':
307
+ return commandRecommendations(args);
278
308
  case 'follow':
279
309
  case 'unfollow':
280
310
  return commandFollow(args);
311
+ case 'inbox':
312
+ return commandInbox(args);
313
+ case 'message':
314
+ return commandMessage(args);
315
+ case 'reply':
316
+ return commandReply(args);
317
+ case 'invoice':
318
+ return commandInvoice(args);
319
+ case 'invoices':
320
+ return commandInvoices(args);
321
+ case 'pay':
322
+ return commandPay(args);
323
+ case 'billing':
324
+ return commandBilling(args);
281
325
  case 'announce':
282
326
  return commandAnnounce(args);
283
327
  case 'instances':
@@ -343,7 +387,7 @@ function queryFrom(args) {
343
387
  return query;
344
388
  }
345
389
  function jobLine(job, where) {
346
- const salary = formatSalary(job.salary);
390
+ const salary = formatPayShort(payOfJob(job));
347
391
  const bits = [job.workplace, job.seniority, job.location, salary, `agents: ${job.agentPolicy}`]
348
392
  .filter((bit) => typeof bit === 'string' && bit !== '');
349
393
  return [
@@ -559,9 +603,12 @@ async function commandShow(args) {
559
603
  const lines = [
560
604
  bold(job.title),
561
605
  `${job.org.name}${job.location === null ? '' : ` - ${job.location}`}`,
562
- dim([job.workplace, job.employmentType, job.seniority, formatSalary(job.salary), `agents: ${job.agentPolicy}`]
606
+ dim([job.workplace, job.employmentType, job.seniority, `agents: ${job.agentPolicy}`]
563
607
  .filter((bit) => typeof bit === 'string' && bit !== '')
564
608
  .join(' | ')),
609
+ `Pay: ${[formatPay(payOfJob(job)) ?? 'not listed', formatMethod(payOfJob(job).method)]
610
+ .filter((bit) => bit !== null)
611
+ .join(', ')}`,
565
612
  '',
566
613
  job.description,
567
614
  '',
@@ -972,6 +1019,8 @@ async function commandPost(args) {
972
1019
  'salaryMax',
973
1020
  'salaryCurrency',
974
1021
  'salaryPeriod',
1022
+ 'payMethod',
1023
+ 'payEquity',
975
1024
  'tags',
976
1025
  'stack',
977
1026
  'agentPolicy',
@@ -980,10 +1029,18 @@ async function commandPost(args) {
980
1029
  if (value !== undefined)
981
1030
  input[key] = value;
982
1031
  }
1032
+ // `--pay "$0.25 per task" --pay "$0.25 per PR"`: one flag per line, the way
1033
+ // the front matter's `pay:` list arrives. Repeated flags accumulate.
1034
+ const payLines = flagList(args, 'pay');
1035
+ if (payLines.length > 0)
1036
+ input['pay'] = payLines;
1037
+ const payMethod = flagString(args, 'pay-method', 'paid-in');
1038
+ if (payMethod !== undefined)
1039
+ input['payMethod'] = payMethod;
983
1040
  // A flag rather than a value, because "unpaid" is a fact about the role and
984
1041
  // not a number. `salary_unpaid: true` in front matter already arrives on its
985
1042
  // own, camel-cased with every other key.
986
- if (flagBool(args, 'salary-unpaid'))
1043
+ if (flagBool(args, 'salary-unpaid', 'unpaid'))
987
1044
  input['salaryUnpaid'] = true;
988
1045
  if (input['org'] === undefined) {
989
1046
  process.stderr.write('Which employer? Pass --org <slug>, or put "org:" in the front matter.\n');
@@ -1090,6 +1147,50 @@ async function commandNews(args) {
1090
1147
  ].join('\n'));
1091
1148
  return out(args, lines.join('\n\n'), result);
1092
1149
  }
1150
+ async function commandRecommend(args) {
1151
+ const slug = args.positional[0] ?? '';
1152
+ const body = args.positional.slice(1).join(' ').trim();
1153
+ if (slug === '' || body === '') {
1154
+ process.stderr.write('agenticjobs recommend <employer-slug> "what you would say" [--candidate] [--as employer]\n');
1155
+ return 1;
1156
+ }
1157
+ const as = flagString(args, 'as');
1158
+ const relationship = flagString(args, 'relationship');
1159
+ const written = await clientFor(args).recommend(flagBool(args, 'candidate') ? { candidate: slug } : { org: slug }, {
1160
+ body,
1161
+ ...(as === undefined ? {} : { as }),
1162
+ ...(relationship === undefined ? {} : { relationship }),
1163
+ });
1164
+ return out(args, `Written, from ${written.recommendation.author.name}. It is pending until ${written.recommendation.subject.name} approves it.`, written);
1165
+ }
1166
+ async function commandRecommendations(args) {
1167
+ const client = clientFor(args);
1168
+ const verb = args.positional[0] ?? '';
1169
+ if (verb === 'approve' || verb === 'reject' || verb === 'withdraw') {
1170
+ const id = args.positional[1] ?? '';
1171
+ if (id === '') {
1172
+ process.stderr.write(`Which one? agenticjobs recommendations ${verb} <id>\n`);
1173
+ return 1;
1174
+ }
1175
+ const result = await client.decideRecommendation(id, verb);
1176
+ return out(args, verb === 'withdraw' ? 'Withdrawn.' : `Now ${result.recommendation?.status ?? verb}.`, result);
1177
+ }
1178
+ const mine = await client.myRecommendations();
1179
+ if (mine.received.length === 0 && mine.given.length === 0) {
1180
+ return out(args, 'None yet, in either direction.', mine);
1181
+ }
1182
+ const line = (item, about) => [
1183
+ `${item.status === 'pending' ? bold('* ') : ' '}${bold(about ? item.author.name : item.subject.name)} ${dim(`[${item.status}]`)}`,
1184
+ ` ${item.body.slice(0, 160).replace(/\n/g, ' ')}`,
1185
+ ` ${dim(item.id)}`,
1186
+ ].join('\n');
1187
+ const lines = [
1188
+ `${mine.pending} waiting for you`,
1189
+ ...(mine.received.length > 0 ? ['', bold('About you'), ...mine.received.map((item) => line(item, true))] : []),
1190
+ ...(mine.given.length > 0 ? ['', bold('You wrote'), ...mine.given.map((item) => line(item, false))] : []),
1191
+ ];
1192
+ return out(args, lines.join('\n'), mine);
1193
+ }
1093
1194
  async function commandFollow(args) {
1094
1195
  const following = args.command === 'follow';
1095
1196
  const candidate = flagBool(args, 'candidate');
@@ -1101,6 +1202,127 @@ async function commandFollow(args) {
1101
1202
  const result = await clientFor(args).setFollow(candidate ? { candidate: slug } : { org: slug }, following);
1102
1203
  return out(args, `${following ? 'Following' : 'Not following'} ${slug}. ${result.followers} ${result.followers === 1 ? 'follower' : 'followers'}.`, result);
1103
1204
  }
1205
+ // --- inbox ----------------------------------------------------------------
1206
+ async function commandInbox(args) {
1207
+ const client = clientFor(args);
1208
+ if (args.positional[0] === 'read') {
1209
+ const id = args.positional[1] ?? '';
1210
+ if (id === '') {
1211
+ process.stderr.write('Which one? agenticjobs inbox read <id>\n');
1212
+ return 1;
1213
+ }
1214
+ const result = await client.thread(id);
1215
+ const lines = result.thread.messages.map((message) => [
1216
+ `${bold(message.mine ? 'You' : message.sender.name)} ${dim(ago(message.createdAt))}${message.kind === 'invoice' ? dim(' (invoice)') : ''}`,
1217
+ ` ${message.body.replace(/\n/g, '\n ')}`,
1218
+ ].join('\n'));
1219
+ const invoices = result.invoices.map((invoice) => ` ${invoice.id} $${invoice.amountUsd} ${invoice.currency} ${invoice.status}`);
1220
+ return out(args, [
1221
+ `${bold(result.thread.subject)} ${dim(`with ${result.thread.with.name}`)}`,
1222
+ '',
1223
+ lines.join('\n\n'),
1224
+ ...(invoices.length === 0 ? [] : ['', bold('Invoices'), ...invoices]),
1225
+ ].join('\n'), result);
1226
+ }
1227
+ const result = await client.inbox();
1228
+ if (result.items.length === 0)
1229
+ return out(args, 'Nothing in the inbox.', result);
1230
+ const lines = result.items.map((thread) => [
1231
+ `${bold(thread.with.name)} ${dim(ago(thread.lastMessageAt))}${thread.unread > 0 ? ` ${bold(`${thread.unread} new`)}` : ''}`,
1232
+ ` ${thread.subject}`,
1233
+ ` ${dim(thread.preview)}`,
1234
+ ` ${dim(thread.id)}`,
1235
+ ].join('\n'));
1236
+ return out(args, lines.join('\n\n'), result);
1237
+ }
1238
+ async function commandMessage(args) {
1239
+ const slug = args.positional[0] ?? '';
1240
+ const body = args.positional.slice(1).join(' ');
1241
+ if (slug === '' || body === '') {
1242
+ process.stderr.write('agenticjobs message <employer-slug> <text> [--candidate] [--job <slug>] [--as <employer>]\n');
1243
+ return 1;
1244
+ }
1245
+ const candidate = flagBool(args, 'candidate');
1246
+ const job = flagString(args, 'job');
1247
+ const as = flagString(args, 'as');
1248
+ const subject = flagString(args, 'subject');
1249
+ const result = await clientFor(args).startThread({
1250
+ ...(candidate ? { candidate: slug } : { employer: slug }),
1251
+ ...(job === undefined || job === null ? {} : { job }),
1252
+ ...(as === undefined || as === null ? {} : { as }),
1253
+ ...(subject === undefined || subject === null ? {} : { subject }),
1254
+ body,
1255
+ });
1256
+ return out(args, `${result.created ? 'Sent' : 'Added to the conversation you already have'}. ${result.url}`, result);
1257
+ }
1258
+ async function commandReply(args) {
1259
+ const id = args.positional[0] ?? '';
1260
+ const body = args.positional.slice(1).join(' ');
1261
+ if (id === '' || body === '') {
1262
+ process.stderr.write('agenticjobs reply <thread-id> <text>\n');
1263
+ return 1;
1264
+ }
1265
+ const result = await clientFor(args).reply(id, body);
1266
+ return out(args, 'Sent.', result);
1267
+ }
1268
+ async function commandInvoice(args) {
1269
+ const id = args.positional[0] ?? '';
1270
+ const amount = args.positional[1] ?? '';
1271
+ if (id === '' || amount === '') {
1272
+ process.stderr.write('agenticjobs invoice <thread-id> <amount-usd> [--currency <chain>] [--for <text>]\n');
1273
+ return 1;
1274
+ }
1275
+ const currency = flagString(args, 'currency');
1276
+ const description = flagString(args, 'for');
1277
+ const result = await clientFor(args).sendInvoice(id, {
1278
+ amount,
1279
+ ...(currency === undefined || currency === null ? {} : { currency }),
1280
+ ...(description === undefined || description === null ? {} : { description }),
1281
+ });
1282
+ return out(args, `Invoice sent: $${result.invoice.amountUsd} in ${result.invoice.currency}. ${dim(result.invoice.id)}`, result);
1283
+ }
1284
+ async function commandInvoices(args) {
1285
+ const result = await clientFor(args).invoices();
1286
+ if (result.items.length === 0)
1287
+ return out(args, 'No invoices.', result);
1288
+ const lines = result.items.map((invoice) => `${bold(`$${invoice.amountUsd}`)} ${invoice.currency} ${invoice.status} ${dim(`from ${invoice.payee.name}, ${ago(invoice.createdAt)}`)}\n ${dim(invoice.id)}`);
1289
+ return out(args, lines.join('\n'), result);
1290
+ }
1291
+ async function commandPay(args) {
1292
+ const id = args.positional[0] ?? '';
1293
+ if (id === '') {
1294
+ process.stderr.write('agenticjobs pay <invoice-id>\n');
1295
+ return 1;
1296
+ }
1297
+ const result = await clientFor(args).payInvoice(id);
1298
+ const invoice = result.invoice;
1299
+ if (invoice.status === 'paid')
1300
+ return out(args, 'Already paid.', result);
1301
+ if (invoice.payment === null)
1302
+ return out(args, 'No quote came back. Try again in a moment.', result);
1303
+ return out(args, [
1304
+ `Pay at ${bold(invoice.payment.url)}`,
1305
+ ...(invoice.payment.amountCrypto === null
1306
+ ? []
1307
+ : [` ${invoice.payment.amountCrypto} ${invoice.currency} to ${invoice.payment.address ?? ''}`]),
1308
+ dim(' The quote lasts a few minutes.'),
1309
+ ].join('\n'), result);
1310
+ }
1311
+ async function commandBilling(args) {
1312
+ const result = await clientFor(args).billing();
1313
+ if (!result.configured)
1314
+ return out(args, 'This board has no billing configured.', result);
1315
+ if (result.account === null) {
1316
+ return out(args, `No CoinPay account connected. Connect one in a browser: ${result.connectUrl ?? ''}`, result);
1317
+ }
1318
+ if (!result.account.usable) {
1319
+ return out(args, `The CoinPay connection has lapsed. Reconnect in a browser: ${result.connectUrl ?? ''}`, result);
1320
+ }
1321
+ const wallets = result.account.wallets;
1322
+ return out(args, wallets.length === 0
1323
+ ? 'Connected, but the CoinPay account has no wallet yet.'
1324
+ : `Connected. Can be paid in: ${wallets.map((w) => `${w.chain} ${dim(w.address)}`).join(', ')}.`, result);
1325
+ }
1104
1326
  async function commandAnnounce(args) {
1105
1327
  const { announceOnce } = await import("../directory/announce.js");
1106
1328
  const { loadConfig: serverConfig, sameOrigin } = await import("../config.js");