@profullstack/agenticjobs 0.11.0 → 0.12.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 +66 -0
- package/dist/cli/args.js +3 -1
- package/dist/cli/args.js.map +1 -1
- package/dist/cli/index.js +149 -0
- 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 +113 -0
- package/dist/client/client.js +25 -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/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 +9 -7
- 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/mail.d.ts +16 -0
- package/dist/core/mail.js +22 -0
- package/dist/core/mail.js.map +1 -1
- package/dist/directory/fetch.d.ts +1 -1
- package/dist/markup/markdown.js +4 -1
- package/dist/markup/markdown.js.map +1 -1
- package/dist/mcp/tools.js +150 -0
- package/dist/mcp/tools.js.map +1 -1
- package/dist/server/app.d.ts +4 -3
- package/dist/server/app.js +7 -4
- package/dist/server/app.js.map +1 -1
- package/dist/server/deps.d.ts +5 -0
- package/dist/server/middleware.js +9 -1
- package/dist/server/middleware.js.map +1 -1
- package/dist/server/routes/api.js +284 -0
- package/dist/server/routes/api.js.map +1 -1
- package/dist/server/routes/discovery.js +19 -0
- 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 +124 -0
- package/dist/server/routes/openapi.js.map +1 -1
- package/dist/server/routes/pages.d.ts +22 -0
- package/dist/server/routes/pages.js +91 -7
- package/dist/server/routes/pages.js.map +1 -1
- package/dist/views/candidates.d.ts +1 -0
- package/dist/views/candidates.js +1 -1
- 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 +9 -0
- package/dist/views/jobs.js +3 -2
- package/dist/views/jobs.js.map +1 -1
- package/dist/views/layout.d.ts +2 -0
- package/dist/views/layout.js +2 -2
- package/dist/views/layout.js.map +1 -1
- package/dist/views/me.d.ts +3 -3
- package/dist/views/me.js +2 -2
- package/dist/views/me.js.map +1 -1
- package/dist/views/updates.d.ts +13 -0
- package/dist/views/updates.js +2 -1
- package/dist/views/updates.js.map +1 -1
- package/migrations/0014_inbox_and_billing.sql +138 -0
- package/package.json +1 -1
- package/web/public/app.css +135 -0
- package/web/public/sw.js +1 -1
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
-- The inbox, and the invoices that travel through it.
|
|
2
|
+
--
|
|
3
|
+
-- A board where the only way to reach somebody is a public comment box gets
|
|
4
|
+
-- used as one: ugig.net's comments filled up with invoices, because an invoice
|
|
5
|
+
-- has to go *somewhere* and that was the only somewhere. So there is no public
|
|
6
|
+
-- commenting here at all. A conversation is private to the people in it, and
|
|
7
|
+
-- an invoice is a message in that conversation with money attached.
|
|
8
|
+
--
|
|
9
|
+
-- Two things are in the schema rather than in a validator:
|
|
10
|
+
--
|
|
11
|
+
-- - A message is bounded. 4000 characters is a long letter; it is not room
|
|
12
|
+
-- for a pasted document, which belongs on a resume or a listing.
|
|
13
|
+
-- - An invoice has one payee and one amount, and its status moves forward.
|
|
14
|
+
-- It is never edited after it is sent: a changed invoice is a new one.
|
|
15
|
+
|
|
16
|
+
-- A conversation. The subject is the one line the list page shows.
|
|
17
|
+
create table if not exists threads (
|
|
18
|
+
id uuid primary key default gen_random_uuid(),
|
|
19
|
+
subject text not null check (length(subject) between 1 and 140),
|
|
20
|
+
-- The listing this is about, when it is about one. Kept when the listing
|
|
21
|
+
-- is deleted: the conversation still happened.
|
|
22
|
+
job_id uuid references jobs (id) on delete set null,
|
|
23
|
+
created_by uuid not null references users (id) on delete cascade,
|
|
24
|
+
created_at timestamptz not null default now(),
|
|
25
|
+
-- Denormalised so the inbox sorts without touching messages.
|
|
26
|
+
last_message_at timestamptz not null default now()
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
-- Who is in it. A person is in a conversation either as themselves or as a
|
|
30
|
+
-- member of an employer; `org_id` says which, and is what the other side sees
|
|
31
|
+
-- as the name. Every member of the employer is a participant, so a message to
|
|
32
|
+
-- "Acme" reaches whoever at Acme is around.
|
|
33
|
+
create table if not exists thread_participants (
|
|
34
|
+
thread_id uuid not null references threads (id) on delete cascade,
|
|
35
|
+
user_id uuid not null references users (id) on delete cascade,
|
|
36
|
+
org_id uuid references organisations (id) on delete set null,
|
|
37
|
+
-- Unread is "a message newer than this". Null means never opened.
|
|
38
|
+
last_read_at timestamptz,
|
|
39
|
+
-- When this person was last emailed about this thread, so a burst of ten
|
|
40
|
+
-- messages is one email rather than ten.
|
|
41
|
+
notified_at timestamptz,
|
|
42
|
+
primary key (thread_id, user_id)
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
create index if not exists thread_participants_user_idx on thread_participants (user_id);
|
|
46
|
+
create index if not exists thread_participants_org_idx on thread_participants (org_id)
|
|
47
|
+
where org_id is not null;
|
|
48
|
+
create index if not exists threads_job_idx on threads (job_id) where job_id is not null;
|
|
49
|
+
|
|
50
|
+
-- An invoice. Its money is settled on CoinPay, to the payee's own wallet; the
|
|
51
|
+
-- board never holds funds. The row here is the source of truth for *which*
|
|
52
|
+
-- conversation it belongs to and *whether it has been paid*; the CoinPay
|
|
53
|
+
-- payment is the source of truth for the chain-level facts, and is re-minted
|
|
54
|
+
-- when a quote expires unpaid, so `coinpay_payment_id` changes over time and
|
|
55
|
+
-- `status` does not move backwards.
|
|
56
|
+
create table if not exists invoices (
|
|
57
|
+
id uuid primary key default gen_random_uuid(),
|
|
58
|
+
thread_id uuid not null references threads (id) on delete cascade,
|
|
59
|
+
-- Who gets paid. Always a person: a payout goes to a wallet somebody
|
|
60
|
+
-- connected, and an employer does not have one.
|
|
61
|
+
payee_id uuid not null references users (id) on delete cascade,
|
|
62
|
+
amount_usd numeric(12, 2) not null check (amount_usd > 0),
|
|
63
|
+
-- The chain the payee chose to be paid on, in CoinPay's spelling:
|
|
64
|
+
-- BTC, ETH, SOL, USDC_POL and so on.
|
|
65
|
+
currency text not null,
|
|
66
|
+
-- The payee's address for that chain, copied at send time. An invoice
|
|
67
|
+
-- settles to the wallet it was sent with, whatever is connected later.
|
|
68
|
+
wallet_address text not null,
|
|
69
|
+
description text not null default '' check (length(description) <= 1000),
|
|
70
|
+
status text not null default 'sent'
|
|
71
|
+
check (status in ('sent', 'paid', 'cancelled')),
|
|
72
|
+
coinpay_payment_id text,
|
|
73
|
+
payment_address text,
|
|
74
|
+
amount_crypto text,
|
|
75
|
+
payment_expires_at timestamptz,
|
|
76
|
+
paid_at timestamptz,
|
|
77
|
+
paid_by uuid references users (id) on delete set null,
|
|
78
|
+
tx_hash text,
|
|
79
|
+
created_at timestamptz not null default now(),
|
|
80
|
+
updated_at timestamptz not null default now()
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
create index if not exists invoices_thread_idx on invoices (thread_id, created_at desc);
|
|
84
|
+
create index if not exists invoices_payee_idx on invoices (payee_id, created_at desc);
|
|
85
|
+
-- The webhook looks a payment up by CoinPay's id and nothing else.
|
|
86
|
+
create unique index if not exists invoices_payment_key on invoices (coinpay_payment_id)
|
|
87
|
+
where coinpay_payment_id is not null;
|
|
88
|
+
|
|
89
|
+
-- A message. `kind` says whether it is words or an invoice; an invoice message
|
|
90
|
+
-- carries the invoice and its body is the description, so a thread reads
|
|
91
|
+
-- top to bottom without joining.
|
|
92
|
+
create table if not exists messages (
|
|
93
|
+
id uuid primary key default gen_random_uuid(),
|
|
94
|
+
thread_id uuid not null references threads (id) on delete cascade,
|
|
95
|
+
sender_id uuid not null references users (id) on delete cascade,
|
|
96
|
+
kind text not null default 'text' check (kind in ('text', 'invoice')),
|
|
97
|
+
body text not null check (length(body) between 1 and 4000),
|
|
98
|
+
invoice_id uuid references invoices (id) on delete set null,
|
|
99
|
+
created_at timestamptz not null default now(),
|
|
100
|
+
constraint messages_invoice_kind check (
|
|
101
|
+
(kind = 'invoice' and invoice_id is not null) or (kind = 'text' and invoice_id is null)
|
|
102
|
+
)
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
create index if not exists messages_thread_idx on messages (thread_id, created_at);
|
|
106
|
+
|
|
107
|
+
-- A CoinPay account somebody connected, so the board can read which wallets
|
|
108
|
+
-- they can be paid to. The tokens are theirs, for their account: the board
|
|
109
|
+
-- reads `userinfo` with them and nothing else.
|
|
110
|
+
--
|
|
111
|
+
-- One CoinPay account attaches to one person here, and the unique index is
|
|
112
|
+
-- what says so. Without it the first account to connect a wallet would be the
|
|
113
|
+
-- one every later profile quietly pays out to.
|
|
114
|
+
create table if not exists coinpay_accounts (
|
|
115
|
+
user_id uuid primary key references users (id) on delete cascade,
|
|
116
|
+
coinpay_sub text not null unique,
|
|
117
|
+
email text,
|
|
118
|
+
name text,
|
|
119
|
+
access_token text not null,
|
|
120
|
+
refresh_token text,
|
|
121
|
+
scope text not null default '',
|
|
122
|
+
expires_at timestamptz not null,
|
|
123
|
+
-- What userinfo last said: [{ address, chain, label }]. Refreshed on
|
|
124
|
+
-- connect and on request, never trusted to be more current than that.
|
|
125
|
+
wallets jsonb not null default '[]'::jsonb,
|
|
126
|
+
connected_at timestamptz not null default now(),
|
|
127
|
+
updated_at timestamptz not null default now()
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
-- An authorization in flight. Server-side rather than in a cookie so the same
|
|
131
|
+
-- flow works from a terminal, and so a state is single-use by deletion.
|
|
132
|
+
create table if not exists coinpay_oauth_states (
|
|
133
|
+
state text primary key,
|
|
134
|
+
user_id uuid not null references users (id) on delete cascade,
|
|
135
|
+
code_verifier text not null,
|
|
136
|
+
redirect text,
|
|
137
|
+
expires_at timestamptz not null
|
|
138
|
+
);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@profullstack/agenticjobs",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"description": "An agent-friendly job board you self-host. It posts its own jobs, never scrapes anyone else's, and answers on every surface: web, API, MCP, CLI, TUI, desktop and PWA. Instances find each other through an open directory.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
package/web/public/app.css
CHANGED
|
@@ -185,6 +185,33 @@ main {
|
|
|
185
185
|
}
|
|
186
186
|
}
|
|
187
187
|
|
|
188
|
+
/*
|
|
189
|
+
* The sidebar is a fixed 20rem track, and what goes in it is exactly the kind
|
|
190
|
+
* of text that cannot break on its own: an email address, a URL, the API line
|
|
191
|
+
* an agent is meant to copy, a skill tag. With no wrapping rule each one is a
|
|
192
|
+
* single word wider than the card, and it walks out over the card's border
|
|
193
|
+
* (the candidate page's contact block did). `anywhere` lets these break at
|
|
194
|
+
* any character; it inherits, so one rule on the aside covers every card.
|
|
195
|
+
*
|
|
196
|
+
* A code block in the sidebar wraps instead of scrolling. A 20rem scroller
|
|
197
|
+
* shows "GET https://example.com/api/v1/cand" and hides the part that
|
|
198
|
+
* matters; the wrapped line is still one line to copy. The page-wide
|
|
199
|
+
* `.code-block` rule keeps its `overflow-x: auto` for the docs, where lines
|
|
200
|
+
* are meant to stay lines.
|
|
201
|
+
*/
|
|
202
|
+
.grid-2 > aside {
|
|
203
|
+
overflow-wrap: anywhere;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
.grid-2 > aside .code-block {
|
|
207
|
+
white-space: pre-wrap;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
.grid-2 > aside .badge {
|
|
211
|
+
white-space: normal;
|
|
212
|
+
text-align: left;
|
|
213
|
+
}
|
|
214
|
+
|
|
188
215
|
.stack {
|
|
189
216
|
display: flex;
|
|
190
217
|
flex-direction: column;
|
|
@@ -867,3 +894,111 @@ a.badge-plain:hover .badge {
|
|
|
867
894
|
.update-link {
|
|
868
895
|
overflow-wrap: anywhere;
|
|
869
896
|
}
|
|
897
|
+
|
|
898
|
+
/* --- inbox ------------------------------------------------------------- */
|
|
899
|
+
|
|
900
|
+
.nav-count {
|
|
901
|
+
display: inline-block;
|
|
902
|
+
min-width: 1.3em;
|
|
903
|
+
padding: 0 0.35em;
|
|
904
|
+
border-radius: 999px;
|
|
905
|
+
background: var(--primary);
|
|
906
|
+
color: var(--primary-foreground);
|
|
907
|
+
font-size: 0.75em;
|
|
908
|
+
line-height: 1.6;
|
|
909
|
+
text-align: center;
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
.thread-list,
|
|
913
|
+
.message-list {
|
|
914
|
+
list-style: none;
|
|
915
|
+
margin: 0;
|
|
916
|
+
padding: 0;
|
|
917
|
+
display: flex;
|
|
918
|
+
flex-direction: column;
|
|
919
|
+
gap: 0.75rem;
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
.thread-subject {
|
|
923
|
+
margin: 0.35rem 0 0.2rem;
|
|
924
|
+
font-weight: 600;
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
.thread-preview {
|
|
928
|
+
margin: 0;
|
|
929
|
+
overflow-wrap: anywhere;
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
.message {
|
|
933
|
+
border: 1px solid var(--border);
|
|
934
|
+
border-radius: var(--radius);
|
|
935
|
+
padding: 0.9rem 1rem;
|
|
936
|
+
background: var(--card);
|
|
937
|
+
max-width: 92%;
|
|
938
|
+
scroll-margin-top: 5rem;
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
/* Yours sit to the right, theirs to the left, so a long thread scans. */
|
|
942
|
+
.message.mine {
|
|
943
|
+
align-self: flex-end;
|
|
944
|
+
background: color-mix(in oklab, var(--primary) 8%, var(--card));
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
.message-meta {
|
|
948
|
+
margin: 0 0 0.35rem;
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
.message-body {
|
|
952
|
+
margin: 0;
|
|
953
|
+
/* Typed in a textarea, so the line breaks are the author's. */
|
|
954
|
+
white-space: pre-wrap;
|
|
955
|
+
overflow-wrap: anywhere;
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
.invoice {
|
|
959
|
+
border: 1px dashed var(--border);
|
|
960
|
+
border-radius: calc(var(--radius) - 2px);
|
|
961
|
+
padding: 0.75rem 0.9rem;
|
|
962
|
+
display: flex;
|
|
963
|
+
flex-direction: column;
|
|
964
|
+
gap: 0.5rem;
|
|
965
|
+
/* Addresses and transaction hashes are one word each. */
|
|
966
|
+
overflow-wrap: anywhere;
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
.invoice-amount {
|
|
970
|
+
font-size: 1.25rem;
|
|
971
|
+
font-weight: 700;
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
.invoice-description {
|
|
975
|
+
margin: 0;
|
|
976
|
+
white-space: pre-wrap;
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
.invoice-form > summary {
|
|
980
|
+
cursor: pointer;
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
.wallet-list code {
|
|
984
|
+
overflow-wrap: anywhere;
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
.table {
|
|
988
|
+
width: 100%;
|
|
989
|
+
border-collapse: collapse;
|
|
990
|
+
font-size: 0.9rem;
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
.table th,
|
|
994
|
+
.table td {
|
|
995
|
+
text-align: left;
|
|
996
|
+
padding: 0.5rem 0.6rem;
|
|
997
|
+
border-bottom: 1px solid var(--border);
|
|
998
|
+
vertical-align: top;
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
.table th {
|
|
1002
|
+
color: var(--muted-foreground);
|
|
1003
|
+
font-weight: 500;
|
|
1004
|
+
}
|
package/web/public/sw.js
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* drifted, which is the only version of this rule that survives contact.
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
-
const VERSION = '
|
|
16
|
+
const VERSION = 'f53a4407';
|
|
17
17
|
const SHELL = `shell-${VERSION}`;
|
|
18
18
|
const ASSETS = ['/assets/app.css', '/assets/tokens.css', '/assets/icon.svg'];
|
|
19
19
|
|