autark-cli 0.5.7 → 0.5.8
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/autark.mjs +148 -3
- package/package.json +1 -1
package/autark.mjs
CHANGED
|
@@ -25,7 +25,23 @@ const RAW_RUNTIME_BASE = process.env.AUTARK_RUNTIME_RAW_BASE
|
|
|
25
25
|
|| 'https://autark.sh/runtime'
|
|
26
26
|
const SKILL_NAMES = ['autark', 'plumcake', 'chrome-relay', 'email', 'outreach', 'email-finder']
|
|
27
27
|
const ECOSYSTEM_CLIS = ['autark-cli', 'plumcake-cli', 'chrome-relay']
|
|
28
|
-
const PROGRAM_FILES = ['start.md', 'double-down.md', 'check.md', 'followup.md']
|
|
28
|
+
const PROGRAM_FILES = ['start.md', 'find_leads.md', 'double-down.md', 'check.md', 'followup.md']
|
|
29
|
+
const LEAD_TEMPLATE = {
|
|
30
|
+
person: {
|
|
31
|
+
full_name: 'Itay Rosen',
|
|
32
|
+
primary_email: 'itay@example.com',
|
|
33
|
+
email_status: 'guessed',
|
|
34
|
+
handles: {
|
|
35
|
+
github: 'itayrosen',
|
|
36
|
+
twitter: 'itayrosen',
|
|
37
|
+
},
|
|
38
|
+
headline: 'Maintainer of parallel-browser-mcp',
|
|
39
|
+
source: 'github',
|
|
40
|
+
},
|
|
41
|
+
lead: {
|
|
42
|
+
angle: 'Maintains a browser automation MCP project, so he is likely to care about real-browser access for agents.',
|
|
43
|
+
},
|
|
44
|
+
}
|
|
29
45
|
|
|
30
46
|
// ============================================================ address reject
|
|
31
47
|
// Two address shapes are bad enough to refuse at the CLI boundary, regardless
|
|
@@ -208,6 +224,11 @@ async function main() {
|
|
|
208
224
|
if (command === 'unpause') return hypothesisPause(rest, 'active')
|
|
209
225
|
if (!command || command === '--help' || command === '-h') return hypothesisUsage()
|
|
210
226
|
}
|
|
227
|
+
if (group === 'lead') {
|
|
228
|
+
if (command === 'add') return leadAdd(rest)
|
|
229
|
+
if (command === 'template') return leadTemplate()
|
|
230
|
+
if (!command || command === 'help' || command === '--help' || command === '-h') return leadUsage()
|
|
231
|
+
}
|
|
211
232
|
if (group === 'run') {
|
|
212
233
|
if (command === 'start') return runStart(rest)
|
|
213
234
|
if (command === 'finish') return runFinish(rest)
|
|
@@ -655,6 +676,69 @@ async function hypothesisStatus(rest) {
|
|
|
655
676
|
console.log(`${label} → ${result.status}`)
|
|
656
677
|
}
|
|
657
678
|
|
|
679
|
+
// =============================================================== v2 leads
|
|
680
|
+
|
|
681
|
+
async function leadAdd(rest) {
|
|
682
|
+
const opts = parseArgs(rest)
|
|
683
|
+
const payload = parseJsonValue(readValue(required(opts.input, '--input @lead.json')), '--input')
|
|
684
|
+
if (!payload || typeof payload !== 'object' || Array.isArray(payload)) throw new Error('--input must be a JSON object')
|
|
685
|
+
if (!payload.lead || typeof payload.lead !== 'object' || !String(payload.lead.angle || '').trim()) {
|
|
686
|
+
throw new Error('lead.angle is required in --input JSON')
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
let hypId = opts['hypothesis-id'] || opts.hypothesis_id
|
|
690
|
+
if (!hypId) {
|
|
691
|
+
const ref = opts.hypothesis || opts._[0]
|
|
692
|
+
if (ref) {
|
|
693
|
+
const [productSlug, code] = splitHypothesisRef(ref)
|
|
694
|
+
const ctx = await api('GET', `/v1/context/${encodeURIComponent(productSlug)}/${encodeURIComponent(code)}`)
|
|
695
|
+
hypId = ctx.hypothesis?.id
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
if (!hypId) throw new Error('pass --hypothesis-id <id> or --hypothesis <slug/Hxx>')
|
|
699
|
+
|
|
700
|
+
const body = {
|
|
701
|
+
...payload,
|
|
702
|
+
source_run_id: opts['run-id'] || opts.run_id || opts.run || payload.source_run_id || undefined,
|
|
703
|
+
}
|
|
704
|
+
const result = await api('POST', `/v1/hypotheses/${encodeURIComponent(hypId)}/leads`, body)
|
|
705
|
+
console.log(JSON.stringify(result))
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
function leadTemplate() {
|
|
709
|
+
printJson(LEAD_TEMPLATE)
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
function leadUsage() {
|
|
713
|
+
console.log(`autark lead
|
|
714
|
+
|
|
715
|
+
template
|
|
716
|
+
print a minimal JSON payload for lead add
|
|
717
|
+
|
|
718
|
+
add --hypothesis-id <id> --input @/tmp/lead.json [--run-id <id>]
|
|
719
|
+
add --hypothesis <slug>/<H01> --input @/tmp/lead.json [--run-id <id>]
|
|
720
|
+
|
|
721
|
+
lead add records one sourced lead: person identity + the angle connecting that
|
|
722
|
+
person to the hypothesis. It sends one ID-first request to the worker and
|
|
723
|
+
prints exactly one compact JSON object to stdout:
|
|
724
|
+
|
|
725
|
+
{"person_id":"...","lead_id":"...","person_existed":false,"lead_existed":false}
|
|
726
|
+
|
|
727
|
+
Minimum payload:
|
|
728
|
+
|
|
729
|
+
${JSON.stringify(LEAD_TEMPLATE, null, 2)}
|
|
730
|
+
|
|
731
|
+
Required:
|
|
732
|
+
- lead.angle
|
|
733
|
+
- enough person identity to dedupe: primary_email, a supported handle, or full_name
|
|
734
|
+
|
|
735
|
+
Optional person fields:
|
|
736
|
+
email_status, headline, source, bio, signals
|
|
737
|
+
|
|
738
|
+
find_leads should write /tmp/lead-N.json and call lead add with --input @file.
|
|
739
|
+
No outreach is sent and no touch rows are written.`)
|
|
740
|
+
}
|
|
741
|
+
|
|
658
742
|
// =============================================================== runs
|
|
659
743
|
|
|
660
744
|
async function runStart(rest) {
|
|
@@ -1214,6 +1298,9 @@ function printProductContext(r) {
|
|
|
1214
1298
|
kv('product.url', r.product.url)
|
|
1215
1299
|
kv('product.visibility', r.product.visibility)
|
|
1216
1300
|
kv('product.hypothesis_count', (r.hypotheses || []).length)
|
|
1301
|
+
kv('product.person_count', r.product.person_count || (r.people || []).length || 0)
|
|
1302
|
+
kv('product.lead_count', r.product.lead_count || (r.leads || []).length || 0)
|
|
1303
|
+
kv('product.ready_lead_count', r.product.ready_lead_count || (r.leads || []).filter((l) => l.status === 'ready').length || 0)
|
|
1217
1304
|
// Product-level operator feedback (newest first). Append-only + timestamped,
|
|
1218
1305
|
// so each entry is a distinct nudge — re-read these every run.
|
|
1219
1306
|
kv('product.feedback_count', (r.feedback || []).length)
|
|
@@ -1229,6 +1316,8 @@ function printProductContext(r) {
|
|
|
1229
1316
|
kv(`hypothesis.${h.code}.run_count`, h.run_count)
|
|
1230
1317
|
kv(`hypothesis.${h.code}.action_count`, h.action_count)
|
|
1231
1318
|
kv(`hypothesis.${h.code}.reply_count`, h.reply_count)
|
|
1319
|
+
kv(`hypothesis.${h.code}.lead_count`, h.lead_count || (h.leads || []).length || 0)
|
|
1320
|
+
kv(`hypothesis.${h.code}.ready_lead_count`, h.ready_lead_count || (h.leads || []).filter((l) => l.status === 'ready').length || 0)
|
|
1232
1321
|
if (h.needs_human_count) kv(`hypothesis.${h.code}.needs_human_count`, h.needs_human_count)
|
|
1233
1322
|
const actions = (h.runs || []).flatMap((run) => run.actions || [])
|
|
1234
1323
|
const by = channelBreakdown(actions)
|
|
@@ -1237,6 +1326,16 @@ function printProductContext(r) {
|
|
|
1237
1326
|
kv(`hypothesis.${h.code}.channel.${ch}.replied`, by[ch].replied)
|
|
1238
1327
|
}
|
|
1239
1328
|
}
|
|
1329
|
+
for (const lead of (r.leads || [])) {
|
|
1330
|
+
const shortL = actionShortId(lead.id)
|
|
1331
|
+
kv(`lead.${shortL}.id`, lead.id)
|
|
1332
|
+
kv(`lead.${shortL}.status`, lead.status)
|
|
1333
|
+
kv(`lead.${shortL}.hypothesis_code`, lead.hypothesis_code)
|
|
1334
|
+
kv(`lead.${shortL}.person_name`, lead.person?.full_name)
|
|
1335
|
+
kv(`lead.${shortL}.person_email`, lead.person?.primary_email)
|
|
1336
|
+
kv(`lead.${shortL}.headline`, lead.person?.headline)
|
|
1337
|
+
kv(`lead.${shortL}.angle`, lead.angle)
|
|
1338
|
+
}
|
|
1240
1339
|
console.log('---')
|
|
1241
1340
|
// ---- narrative ----
|
|
1242
1341
|
console.log(`# ${r.product.slug} — ${r.product.name}\n`)
|
|
@@ -1261,7 +1360,18 @@ function printProductContext(r) {
|
|
|
1261
1360
|
.map((ch) => `${ch} ${by[ch].replied}/${by[ch].sent}`)
|
|
1262
1361
|
.join(' ')
|
|
1263
1362
|
const tail = channelSummary ? ` — replies/sends by channel: ${channelSummary}` : ''
|
|
1264
|
-
console.log(`- [${h.status}] ${h.code} — ${h.title} (runs: ${h.run_count}, actions: ${h.action_count || 0}, replies: ${h.reply_count || 0})${tail}`)
|
|
1363
|
+
console.log(`- [${h.status}] ${h.code} — ${h.title} (runs: ${h.run_count}, actions: ${h.action_count || 0}, replies: ${h.reply_count || 0}, leads: ${h.lead_count || 0})${tail}`)
|
|
1364
|
+
}
|
|
1365
|
+
}
|
|
1366
|
+
if (r.leads?.length) {
|
|
1367
|
+
console.log(`\n## Recent leads (${r.leads.length})\n`)
|
|
1368
|
+
for (const lead of r.leads) {
|
|
1369
|
+
const person = lead.person || {}
|
|
1370
|
+
const label = person.full_name || person.primary_email || Object.entries(person.handles || {})[0]?.join(':') || lead.person_id || 'unknown person'
|
|
1371
|
+
const hyp = lead.hypothesis_code ? `${lead.hypothesis_code} ` : ''
|
|
1372
|
+
const headline = person.headline ? ` — ${person.headline}` : ''
|
|
1373
|
+
console.log(`- [${lead.status}] ${hyp}${label}${headline}`)
|
|
1374
|
+
if (lead.angle) console.log(` angle: ${lead.angle}`)
|
|
1265
1375
|
}
|
|
1266
1376
|
}
|
|
1267
1377
|
}
|
|
@@ -1274,6 +1384,8 @@ function printHypothesisContext(result) {
|
|
|
1274
1384
|
kv('hypothesis.code', result.hypothesis.code)
|
|
1275
1385
|
kv('hypothesis.title', result.hypothesis.title)
|
|
1276
1386
|
kv('hypothesis.status', result.hypothesis.status)
|
|
1387
|
+
kv('hypothesis.lead_count', result.hypothesis.lead_count || (result.leads || []).length || 0)
|
|
1388
|
+
kv('hypothesis.ready_lead_count', result.hypothesis.ready_lead_count || (result.leads || []).filter((l) => l.status === 'ready').length || 0)
|
|
1277
1389
|
// Feedback — operator nudges left on this hypothesis, oldest first. The
|
|
1278
1390
|
// worker returns them; previously the CLI silently dropped them, so agents
|
|
1279
1391
|
// never saw the operator's running corrections. Now surface them up top
|
|
@@ -1297,9 +1409,11 @@ function printHypothesisContext(result) {
|
|
|
1297
1409
|
}
|
|
1298
1410
|
for (const run of (result.runs || [])) {
|
|
1299
1411
|
const shortRun = actionShortId(run.id)
|
|
1412
|
+
const runLeads = (result.leads || []).filter((l) => l.source_run_id === run.id)
|
|
1300
1413
|
kv(`run.${shortRun}.id`, run.id)
|
|
1301
1414
|
kv(`run.${shortRun}.run_number`, run.run_number)
|
|
1302
1415
|
kv(`run.${shortRun}.started_at`, run.started_at)
|
|
1416
|
+
kv(`run.${shortRun}.lead_count`, runLeads.length)
|
|
1303
1417
|
if (run.finished_at) kv(`run.${shortRun}.finished_at`, run.finished_at)
|
|
1304
1418
|
for (const a of (run.actions || [])) {
|
|
1305
1419
|
const shortA = actionShortId(a.id)
|
|
@@ -1326,6 +1440,16 @@ function printHypothesisContext(result) {
|
|
|
1326
1440
|
}
|
|
1327
1441
|
}
|
|
1328
1442
|
}
|
|
1443
|
+
for (const lead of (result.leads || [])) {
|
|
1444
|
+
const shortL = actionShortId(lead.id)
|
|
1445
|
+
kv(`lead.${shortL}.id`, lead.id)
|
|
1446
|
+
kv(`lead.${shortL}.status`, lead.status)
|
|
1447
|
+
kv(`lead.${shortL}.source_run_id`, lead.source_run_id)
|
|
1448
|
+
kv(`lead.${shortL}.person_name`, lead.person?.full_name)
|
|
1449
|
+
kv(`lead.${shortL}.person_email`, lead.person?.primary_email)
|
|
1450
|
+
kv(`lead.${shortL}.headline`, lead.person?.headline)
|
|
1451
|
+
kv(`lead.${shortL}.angle`, lead.angle)
|
|
1452
|
+
}
|
|
1329
1453
|
console.log('---')
|
|
1330
1454
|
// ---- narrative ----
|
|
1331
1455
|
console.log(`# ${result.product.slug}/${result.hypothesis.code} — ${result.hypothesis.title}\n`)
|
|
@@ -1338,9 +1462,21 @@ function printHypothesisContext(result) {
|
|
|
1338
1462
|
console.log(`- [${when}]${tag} ${f.text}`)
|
|
1339
1463
|
}
|
|
1340
1464
|
}
|
|
1465
|
+
if (result.leads?.length) {
|
|
1466
|
+
console.log(`\n\n## Leads (${result.leads.length})\n`)
|
|
1467
|
+
for (const lead of result.leads) {
|
|
1468
|
+
const person = lead.person || {}
|
|
1469
|
+
const label = person.full_name || person.primary_email || Object.entries(person.handles || {})[0]?.join(':') || lead.person_id || 'unknown person'
|
|
1470
|
+
const headline = person.headline ? ` — ${person.headline}` : ''
|
|
1471
|
+
const runTag = lead.source_run_id ? ` (run ${actionShortId(lead.source_run_id)})` : ''
|
|
1472
|
+
console.log(`- [${lead.status}] ${label}${headline}${runTag}`)
|
|
1473
|
+
if (lead.angle) console.log(` angle: ${lead.angle}`)
|
|
1474
|
+
}
|
|
1475
|
+
}
|
|
1341
1476
|
for (const run of (result.runs || [])) {
|
|
1477
|
+
const runLeads = (result.leads || []).filter((l) => l.source_run_id === run.id)
|
|
1342
1478
|
console.log(`\n\n## Run ${run.run_number} (${run.id})`)
|
|
1343
|
-
console.log(`started: ${run.started_at}${run.finished_at ? ` finished: ${run.finished_at}` : ' (in progress)'}`)
|
|
1479
|
+
console.log(`started: ${run.started_at}${run.finished_at ? ` finished: ${run.finished_at}` : ' (in progress)'}${runLeads.length ? ` leads sourced: ${runLeads.length}` : ''}`)
|
|
1344
1480
|
if (run.actions?.length) {
|
|
1345
1481
|
console.log(`\nActions:`)
|
|
1346
1482
|
for (const a of run.actions) {
|
|
@@ -1423,6 +1559,14 @@ function readValue(value) {
|
|
|
1423
1559
|
return String(value)
|
|
1424
1560
|
}
|
|
1425
1561
|
|
|
1562
|
+
function parseJsonValue(value, label = 'json') {
|
|
1563
|
+
try {
|
|
1564
|
+
return JSON.parse(value)
|
|
1565
|
+
} catch (e) {
|
|
1566
|
+
throw new Error(`${label} must be valid JSON: ${e.message}`)
|
|
1567
|
+
}
|
|
1568
|
+
}
|
|
1569
|
+
|
|
1426
1570
|
function listOpt(value, label) {
|
|
1427
1571
|
if (value === undefined || value === null || value === '') {
|
|
1428
1572
|
if (label) throw new Error(`${label} is required`)
|
|
@@ -1509,6 +1653,7 @@ function usage() {
|
|
|
1509
1653
|
product pause|unpause stop / resume cron on a product
|
|
1510
1654
|
hypothesis create|status create or update hypotheses
|
|
1511
1655
|
hypothesis pause|unpause stop / resume work on a hypothesis
|
|
1656
|
+
lead add|template record sourced v2 leads
|
|
1512
1657
|
run start|finish start / finish a run
|
|
1513
1658
|
log action record one outreach touch
|
|
1514
1659
|
action escalate flag an action for human attention
|
package/package.json
CHANGED