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.
Files changed (2) hide show
  1. package/autark.mjs +148 -3
  2. 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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "autark-cli",
3
- "version": "0.5.7",
3
+ "version": "0.5.8",
4
4
  "description": "CLI for autark \u2014 hypothesis-driven product runbooks. Track products, hypotheses, runs, and actions from the terminal.",
5
5
  "type": "module",
6
6
  "license": "MIT",