autark-cli 0.7.0 → 0.7.2

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 +77 -26
  2. package/package.json +1 -1
package/autark.mjs CHANGED
@@ -23,6 +23,19 @@ const CREDS_PATH = process.env.AUTARK_CREDENTIALS || path.join(AUTARK_HOME, 'cre
23
23
  // main().catch() call below.
24
24
  const RAW_RUNTIME_BASE = process.env.AUTARK_RUNTIME_RAW_BASE
25
25
  || 'https://autark.sh/runtime'
26
+ // ----------------------------------------------------------- update styling
27
+ // The autark vibe: calm, aligned, lowercase. Color only when a human is
28
+ // looking (TTY, no NO_COLOR); agents piping output get plain text.
29
+ const TTY = process.stdout.isTTY && !process.env.NO_COLOR
30
+ const paint = (code) => (s) => (TTY ? `\x1b[${code}m${s}\x1b[0m` : String(s))
31
+ const dim = paint('2')
32
+ const green = paint('32')
33
+ const gold = paint('33')
34
+ const red = paint('31')
35
+ const bold = paint('1')
36
+ const shortVer = (v) => (typeof v === 'string' && /^[0-9a-f-]{36}$/.test(v) ? v.slice(0, 8) : v)
37
+ const pad = (s, n) => String(s).padEnd(n)
38
+
26
39
  const SKILL_NAMES = ['autark', 'plumcake', 'chrome-relay', 'email', 'outreach', 'email-finder']
27
40
  const SKILL_AGENTS = ['claude-code', 'codex', 'pi', 'opencode']
28
41
  const ECOSYSTEM_CLIS = ['autark-cli', 'plumcake-cli', 'chrome-relay']
@@ -361,26 +374,28 @@ async function update(rest) {
361
374
  const log = (msg) => console.log(msg)
362
375
  const sub = (msg) => console.log(` ${msg}`)
363
376
 
364
- log('autark update')
377
+ log('')
378
+ log(` ${bold('autark')} ${dim('· update')}`)
379
+ log('')
365
380
 
366
381
  // ---- Step 0: self-update CLI ecosystem ---------------------------------
367
382
  if (!skipSelfUpdate) {
368
- log('checking CLI ecosystem versions…')
369
383
  const upgrades = []
370
384
  for (const pkg of ECOSYSTEM_CLIS) {
371
385
  const installed = readInstalledVersion(pkg)
372
386
  const latest = await fetchNpmLatest(pkg)
373
- if (!latest) { sub(`! ${pkg}: could not reach npm registry, skipping`); continue }
387
+ if (!latest) { sub(`${pad(pkg, 14)} ${gold('!')} npm unreachable, skipping`); continue }
374
388
  if (!installed || cmpVersions(installed, latest) < 0) {
375
389
  upgrades.push({ pkg, installed, latest })
376
- sub(`${pkg}: ${installed || '(not installed)'} → ${latest}`)
390
+ sub(`${pad(pkg, 14)} ${installed || dim('(not installed)')} ${gold('' + latest)}`)
377
391
  } else {
378
- sub(`${pkg}: ${installed} (current)`)
392
+ sub(`${pad(pkg, 14)} ${pad(installed, 9)} ${green('✓')} ${dim('current')}`)
379
393
  }
380
394
  }
381
395
 
382
396
  if (upgrades.length && !dryRun) {
383
- log(`upgrading ${upgrades.length} CLI(s) via pnpm…`)
397
+ log('')
398
+ log(` ${gold('upgrading')} ${dim(`${upgrades.length} cli${upgrades.length === 1 ? '' : 's'} via pnpm`)}`)
384
399
  const args = ['add', '-g', ...upgrades.map((u) => `${u.pkg}@latest`)]
385
400
  const res = await spawnSync('pnpm', args)
386
401
  if (res.status !== 0) {
@@ -408,7 +423,6 @@ async function update(rest) {
408
423
  }
409
424
 
410
425
  // ---- Step 1+2: compare /v1/me + refresh runtime + credentials ---------
411
- log('fetching /v1/me…')
412
426
  const me = await api('GET', '/v1/me')
413
427
  const localVersion = readLocalVersion()
414
428
  const localInbox = (loadCredentials() || {}).agentmail_email || ''
@@ -416,11 +430,13 @@ async function update(rest) {
416
430
  const runtimeBehind = me.runtime_version && me.runtime_version !== localVersion
417
431
  const inboxBehind = me.agentmail_email && me.agentmail_email !== localInbox
418
432
 
419
- sub(`runtime_version local=${localVersion || '<none>'} cloud=${me.runtime_version} ${runtimeBehind ? '→ refresh' : 'ok'}`)
420
- sub(`agentmail_email local=${localInbox || '<none>'} cloud=${me.agentmail_email} ${inboxBehind ? '→ rotate' : 'ok'}`)
433
+ sub(`${pad('runtime', 14)} ${pad(shortVer(localVersion) || dim('<none>'), 9)} ${runtimeBehind ? gold(`→ ${shortVer(me.runtime_version)}`) : `${green('')} ${dim('matches cloud')}`}`)
434
+ sub(`${pad('inbox', 14)} ${localInbox || dim('<none>')} ${inboxBehind ? gold('→ rotate') : `${green('')} ${dim('matches cloud')}`}`)
421
435
 
422
436
  if (!runtimeBehind && !inboxBehind) {
423
- log('already up to date')
437
+ log('')
438
+ log(` ${green('✓')} already up to date`)
439
+ log('')
424
440
  return
425
441
  }
426
442
  if (dryRun) {
@@ -431,25 +447,26 @@ async function update(rest) {
431
447
  // Refresh runtime files + skills (all-or-nothing for rollback safety).
432
448
  if (runtimeBehind) {
433
449
  try {
434
- log('refreshing runtime files…')
450
+ log('')
451
+ sub(`${dim('refreshing')} runtime files`)
435
452
  await refreshRuntimeFiles()
436
- log('refreshing kstack skills…')
453
+ sub(`${dim('refreshing')} kstack skills`)
437
454
  await refreshSkills()
438
- log('reloading local scheduler…')
455
+ sub(`${dim('reloading')} local scheduler`)
439
456
  await reloadLocalScheduler()
440
457
  // Only stamp the version AFTER every step succeeded.
441
458
  writeLocalVersion(me.runtime_version)
442
- sub(`stamped ~/.autark/runtime/version.txt → ${me.runtime_version}`)
459
+ sub(`${dim('stamped')} ${shortVer(me.runtime_version)} ${dim('→ ~/.autark/runtime/version.txt')}`)
443
460
  } catch (e) {
444
- console.error(` runtime refresh failed: ${e.message}`)
445
- console.error(' rolled back: version stamp NOT written. Re-run autark update.')
461
+ console.error(` ${red('✗')} runtime refresh failed: ${e.message}`)
462
+ console.error(` ${dim('rolled back version stamp not written. re-run autark update.')}`)
446
463
  process.exit(2)
447
464
  }
448
465
  }
449
466
 
450
467
  // Rotate credentials (separate from runtime refresh — independent stream).
451
468
  if (inboxBehind) {
452
- log('rotating AgentMail credentials…')
469
+ sub(`${dim('rotating')} agentmail credentials`)
453
470
  const rotated = await api('POST', '/v1/inbox/rotate', {})
454
471
  saveCredentials({
455
472
  ...loadCredentials(),
@@ -458,10 +475,12 @@ async function update(rest) {
458
475
  agentmail_token: rotated.agentmail_token,
459
476
  rotated_at: new Date().toISOString(),
460
477
  })
461
- sub(`new inbox: ${rotated.agentmail_email}`)
478
+ sub(`${dim('new inbox')} ${rotated.agentmail_email}`)
462
479
  }
463
480
 
464
- log(`autark updated → ${me.runtime_version}`)
481
+ log('')
482
+ log(` ${green('✓')} updated ${dim('→')} ${shortVer(me.runtime_version)}`)
483
+ log('')
465
484
  }
466
485
 
467
486
  // -----------------------------------------------------------------------
@@ -724,19 +743,42 @@ async function leadStatus(rest) {
724
743
  // status. "Who replied?" = `autark lead list <slug> --status replied`.
725
744
  async function leadList(rest) {
726
745
  const opts = parseArgs(rest)
727
- const product = required(opts.product || opts._[0], 'product slug')
746
+ // Accept <slug>, <slug>/Hxx, or <slug> --hypothesis Hxx. The /Hxx form reads
747
+ // a hypothesis the same way it's written (lead add / run start / context).
748
+ // --hypothesis wins if both are supplied.
749
+ const ref = required(opts.product || opts._[0], 'product slug (or <slug>/Hxx)')
750
+ let product = ref
751
+ let hypothesis = opts.hypothesis || opts.hyp || ''
752
+ if (ref.includes('/')) {
753
+ const [slug, code] = ref.split('/')
754
+ product = slug
755
+ if (!hypothesis) hypothesis = code
756
+ }
728
757
  const status = opts.status || ''
729
- const qs = `?product=${encodeURIComponent(product)}${status ? `&status=${encodeURIComponent(status)}` : ''}`
758
+ const qs = `?product=${encodeURIComponent(product)}`
759
+ + (status ? `&status=${encodeURIComponent(status)}` : '')
760
+ + (hypothesis ? `&hypothesis=${encodeURIComponent(hypothesis)}` : '')
730
761
  const data = await api('GET', `/v1/leads${qs}`)
731
762
  if (opts.json) { console.log(JSON.stringify(data, null, 2)); return }
732
- if (!data.leads.length) { console.log(`no leads${status ? ` with status ${status}` : ''} in ${product}`); return }
733
- for (const l of data.leads) {
763
+ const scope = `${product}${hypothesis ? `/${hypothesis}` : ''}`
764
+ if (!data.leads.length) { console.log(`no leads${status ? ` with status ${status}` : ''} in ${scope}`); return }
765
+ const printRow = (l) => {
734
766
  const who = [l.full_name, l.email && `<${l.email}>`].filter(Boolean).join(' ')
735
767
  console.log(`${l.lead_id} ${l.status}${l.hypothesis ? ` ${l.hypothesis}` : ''} ${who}`)
736
768
  if (l.replied_at) console.log(` replied_at=${l.replied_at}${l.last_in_at ? ` last_in=${l.last_in_at}` : ''}`)
737
769
  if (l.thread_ref) console.log(` thread=${l.thread_ref} inbox=${l.inbox_ref || ''}`)
738
770
  if (l.angle) console.log(` angle: ${l.angle.length > 140 ? l.angle.slice(0, 140) + '…' : l.angle}`)
739
771
  }
772
+ // Same split as the dashboard replies tab: their message is the latest
773
+ // unanswered word → the ball is in our court.
774
+ const needsRows = data.leads.filter((l) => l.needs_reply)
775
+ const otherRows = data.leads.filter((l) => !l.needs_reply)
776
+ if (needsRows.length) {
777
+ console.log(`— needs your reply · ${needsRows.length}`)
778
+ for (const l of needsRows) printRow(l)
779
+ if (otherRows.length) console.log(`— answered / waiting · ${otherRows.length}`)
780
+ }
781
+ for (const l of otherRows) printRow(l)
740
782
  console.error(`${data.count} lead(s). Next: autark lead show <id> · autark mail thread <thread> · autark mail reply --lead-id <id>`)
741
783
  }
742
784
 
@@ -818,9 +860,12 @@ function leadUsage() {
818
860
  add --hypothesis-id <id> --input @/tmp/lead.json [--run-id <id>]
819
861
  add --hypothesis <slug>/<H01> --input @/tmp/lead.json [--run-id <id>]
820
862
 
821
- list <slug> [--status replied|contacted|ready|...] [--json]
863
+ list <slug>[/Hxx] [--status replied,ready|contacted|...] [--hypothesis Hxx] [--json]
822
864
  the front door: one line per lead, ids first, newest replies on top.
823
- "who replied?" = lead list <slug> --status replied
865
+ scope to one bet with <slug>/Hxx (or --hypothesis Hxx); --status takes a
866
+ comma list. both filters optional and combine.
867
+ "who replied?" = lead list <slug> --status replied
868
+ "ready leads in H23?" = lead list <slug>/H23 --status ready
824
869
 
825
870
  show <lead-id>
826
871
  person + bet + status + the ordered touch log with ids — run this
@@ -1130,6 +1175,10 @@ async function mailReply(rest, mode) {
1130
1175
  const creds = requireAgentmailCredentials()
1131
1176
  const messageId = required(opts['message-id'] || opts.message_id || opts._[0], '--message-id')
1132
1177
  const body = mailBody(opts)
1178
+ // Reply addresses the SENDER of the target message. Following up on your
1179
+ // own last message therefore self-sends unless you override the recipient.
1180
+ // --to forces who receives the reply (the follow-up case).
1181
+ if (opts.to !== undefined) body.to = listOpt(opts.to, '--to')
1133
1182
  const endpoint = mode === 'reply-all' ? 'reply-all' : 'reply'
1134
1183
  const result = await agentmailRequest('POST', `/inboxes/${encodeURIComponent(creds.inboxId)}/messages/${encodeURIComponent(messageId)}/${endpoint}`, body)
1135
1184
  const action = await maybeLogMailAction(opts, {
@@ -1702,7 +1751,9 @@ function mailUsage() {
1702
1751
 
1703
1752
  setup --prefix <name> [--force]
1704
1753
  send --to <email[,email]> --subject <s> [--text @body.txt] [--html @body.html] [--cc <e>] [--bcc <e>] [--reply-to <e>] [--label <l>] [--attachment <a>] [--run-id <id>] [--lead-id <id>] [--dry-run]
1705
- reply --message-id <id> [--text @reply.txt] [--html @reply.html] [--run-id <id>] [--lead-id <id>]
1754
+ reply --message-id <id> [--to <email>] [--text @reply.txt] [--html @reply.html] [--run-id <id>] [--lead-id <id>]
1755
+ --to overrides the recipient — REQUIRED when following up on your
1756
+ own message (plain reply answers its sender: you)
1706
1757
  reply-all --message-id <id> [--text @reply.txt] [--html @reply.html] [--run-id <id>] [--lead-id <id>]
1707
1758
  forward --message-id <id> --to <email> [--text @body.txt] [--html @body.html] [--run-id <id>] [--lead-id <id>]
1708
1759
  threads [--limit N]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "autark-cli",
3
- "version": "0.7.0",
3
+ "version": "0.7.2",
4
4
  "description": "CLI for autark — hypothesis-driven product runbooks. Track products, hypotheses, runs, and actions from the terminal.",
5
5
  "type": "module",
6
6
  "license": "MIT",