@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.
- package/README.md +136 -1
- package/dist/cli/args.js +4 -2
- package/dist/cli/args.js.map +1 -1
- package/dist/cli/index.js +227 -5
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/jobfile.js +38 -2
- package/dist/cli/jobfile.js.map +1 -1
- package/dist/client/client.d.ts +157 -0
- package/dist/client/client.js +44 -0
- package/dist/client/client.js.map +1 -1
- package/dist/config.d.ts +35 -1
- package/dist/config.js +33 -1
- package/dist/config.js.map +1 -1
- package/dist/core/agentwriter.js +22 -5
- package/dist/core/agentwriter.js.map +1 -1
- package/dist/core/applications.js +1 -1
- package/dist/core/applications.js.map +1 -1
- package/dist/core/candidates.js +3 -5
- package/dist/core/candidates.js.map +1 -1
- package/dist/core/capacity.d.ts +1 -1
- package/dist/core/capacity.js +11 -19
- package/dist/core/capacity.js.map +1 -1
- package/dist/core/coinpay.d.ts +143 -0
- package/dist/core/coinpay.js +417 -0
- package/dist/core/coinpay.js.map +1 -0
- package/dist/core/import.js +19 -9
- package/dist/core/import.js.map +1 -1
- package/dist/core/inbox.d.ts +155 -0
- package/dist/core/inbox.js +353 -0
- package/dist/core/inbox.js.map +1 -0
- package/dist/core/invoices.d.ts +111 -0
- package/dist/core/invoices.js +292 -0
- package/dist/core/invoices.js.map +1 -0
- package/dist/core/jobs.d.ts +24 -6
- package/dist/core/jobs.js +116 -31
- package/dist/core/jobs.js.map +1 -1
- package/dist/core/mail.d.ts +16 -0
- package/dist/core/mail.js +22 -0
- package/dist/core/mail.js.map +1 -1
- package/dist/core/recommendations.d.ts +134 -0
- package/dist/core/recommendations.js +356 -0
- package/dist/core/recommendations.js.map +1 -0
- package/dist/core/updates.d.ts +15 -3
- package/dist/core/updates.js +58 -24
- package/dist/core/updates.js.map +1 -1
- package/dist/core/zip.js +29 -4
- package/dist/core/zip.js.map +1 -1
- package/dist/directory/federate.js +3 -1
- package/dist/directory/federate.js.map +1 -1
- package/dist/directory/fetch.d.ts +1 -1
- package/dist/markup/markdown.js +6 -2
- package/dist/markup/markdown.js.map +1 -1
- package/dist/markup/resume.js +18 -5
- package/dist/markup/resume.js.map +1 -1
- package/dist/mcp/tools.js +249 -7
- package/dist/mcp/tools.js.map +1 -1
- package/dist/schema/index.d.ts +1 -0
- package/dist/schema/index.js +1 -0
- package/dist/schema/index.js.map +1 -1
- package/dist/schema/instance.js +29 -0
- package/dist/schema/instance.js.map +1 -1
- package/dist/schema/job.d.ts +12 -0
- package/dist/schema/job.js +17 -3
- package/dist/schema/job.js.map +1 -1
- package/dist/schema/jsonld.js +7 -6
- package/dist/schema/jsonld.js.map +1 -1
- package/dist/schema/pay.d.ts +173 -0
- package/dist/schema/pay.js +668 -0
- package/dist/schema/pay.js.map +1 -0
- package/dist/server/app.d.ts +4 -3
- package/dist/server/app.js +10 -5
- package/dist/server/app.js.map +1 -1
- package/dist/server/deps.d.ts +5 -0
- package/dist/server/middleware.d.ts +11 -1
- package/dist/server/middleware.js +26 -3
- package/dist/server/middleware.js.map +1 -1
- package/dist/server/routes/api.js +437 -7
- package/dist/server/routes/api.js.map +1 -1
- package/dist/server/routes/discovery.js +29 -8
- package/dist/server/routes/discovery.js.map +1 -1
- package/dist/server/routes/inbox.d.ts +9 -0
- package/dist/server/routes/inbox.js +223 -0
- package/dist/server/routes/inbox.js.map +1 -0
- package/dist/server/routes/openapi.js +260 -2
- package/dist/server/routes/openapi.js.map +1 -1
- package/dist/server/routes/pages.d.ts +22 -0
- package/dist/server/routes/pages.js +244 -19
- package/dist/server/routes/pages.js.map +1 -1
- package/dist/tui/views.js +9 -3
- package/dist/tui/views.js.map +1 -1
- package/dist/views/candidates.d.ts +7 -0
- package/dist/views/candidates.js +3 -2
- package/dist/views/candidates.js.map +1 -1
- package/dist/views/docs.js +10 -1
- package/dist/views/docs.js.map +1 -1
- package/dist/views/inbox.d.ts +90 -0
- package/dist/views/inbox.js +73 -0
- package/dist/views/inbox.js.map +1 -0
- package/dist/views/jobs.d.ts +25 -0
- package/dist/views/jobs.js +35 -12
- package/dist/views/jobs.js.map +1 -1
- package/dist/views/layout.d.ts +24 -0
- package/dist/views/layout.js +20 -2
- package/dist/views/layout.js.map +1 -1
- package/dist/views/me.d.ts +10 -3
- package/dist/views/me.js +3 -2
- package/dist/views/me.js.map +1 -1
- package/dist/views/post.d.ts +18 -0
- package/dist/views/post.js +39 -7
- package/dist/views/post.js.map +1 -1
- package/dist/views/recommendations.d.ts +43 -0
- package/dist/views/recommendations.js +26 -0
- package/dist/views/recommendations.js.map +1 -0
- package/dist/views/updates.d.ts +13 -0
- package/dist/views/updates.js +6 -5
- package/dist/views/updates.js.map +1 -1
- package/docs/openjob.md +63 -0
- package/migrations/0014_inbox_and_billing.sql +138 -0
- package/migrations/0015_pay_lines.sql +38 -0
- package/migrations/0016_recommendations.sql +65 -0
- package/package.json +1 -1
- package/web/public/app.css +192 -2
- 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
|
|
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
|
|
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
|
|
128
|
+
const parsed = Number(raw);
|
|
127
129
|
return Number.isFinite(parsed) ? parsed : undefined;
|
|
128
130
|
}
|
|
129
131
|
//# sourceMappingURL=args.js.map
|
package/dist/cli/args.js.map
CHANGED
|
@@ -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
|
|
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 =
|
|
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,
|
|
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");
|