autark-cli 0.5.2 → 0.5.3

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 +93 -5
  2. package/package.json +1 -1
package/autark.mjs CHANGED
@@ -320,7 +320,7 @@ function settingsUsage() {
320
320
  // via pnpm. Re-exec autark if its own version moved.
321
321
  // 1. GET /v1/me — compare runtime_version + agentmail_email against local.
322
322
  // 2. If runtime_version differs: curl the canonical files from the repo,
323
- // refresh the 6 kstack skills via pnpm dlx, reload launchd, stamp the
323
+ // refresh the 6 kstack skills via pnpm dlx, reload the local scheduler, stamp the
324
324
  // new version into ~/.autark/runtime/version.txt.
325
325
  // 3. If agentmail_email differs: POST /v1/inbox/rotate to re-mint a fresh
326
326
  // inbox-scoped key, rewrite credentials.json.
@@ -413,8 +413,8 @@ async function update(rest) {
413
413
  await refreshRuntimeFiles()
414
414
  log('refreshing kstack skills…')
415
415
  await refreshSkills()
416
- log('reloading launchd…')
417
- await reloadLaunchd()
416
+ log('reloading local scheduler…')
417
+ await reloadLocalScheduler()
418
418
  // Only stamp the version AFTER every step succeeded.
419
419
  writeLocalVersion(me.runtime_version)
420
420
  sub(`stamped ~/.autark/runtime/version.txt → ${me.runtime_version}`)
@@ -512,6 +512,8 @@ async function refreshRuntimeFiles() {
512
512
  const agentSh = path.join(AUTARK_HOME, 'agent.sh')
513
513
  await curlFile(`${RAW_RUNTIME_BASE}/agent.sh`, agentSh)
514
514
  fs.chmodSync(agentSh, 0o755)
515
+ const agentPs1 = path.join(AUTARK_HOME, 'agent.ps1')
516
+ await curlFile(`${RAW_RUNTIME_BASE}/agent.ps1`, agentPs1)
515
517
  }
516
518
 
517
519
  async function refreshSkills() {
@@ -547,6 +549,29 @@ async function reloadLaunchd() {
547
549
  await spawnSync('launchctl', ['load', plist])
548
550
  }
549
551
 
552
+ async function reloadWindowsTask() {
553
+ if (process.platform !== 'win32') return
554
+ const canonicalHome = path.join(os.homedir(), '.autark')
555
+ if (path.resolve(AUTARK_HOME) !== path.resolve(canonicalHome)) {
556
+ return
557
+ }
558
+ const agentPs1 = path.join(AUTARK_HOME, 'agent.ps1')
559
+ if (!fs.existsSync(agentPs1)) return
560
+ const taskName = 'Autark Runner'
561
+ const ps = [
562
+ `$action = New-ScheduledTaskAction -Execute 'powershell.exe' -Argument '-NoProfile -ExecutionPolicy Bypass -File "${agentPs1.replaceAll('"', '`"')}"'`,
563
+ `$morning = New-ScheduledTaskTrigger -Daily -At 9am`,
564
+ `$evening = New-ScheduledTaskTrigger -Daily -At 7pm`,
565
+ `Register-ScheduledTask -TaskName '${taskName}' -Action $action -Trigger @($morning, $evening) -Description 'Autark daily runner' -Force | Out-Null`,
566
+ ].join('; ')
567
+ await spawnSync('powershell.exe', ['-NoProfile', '-ExecutionPolicy', 'Bypass', '-Command', ps])
568
+ }
569
+
570
+ async function reloadLocalScheduler() {
571
+ await reloadLaunchd()
572
+ await reloadWindowsTask()
573
+ }
574
+
550
575
  // Promise-wrapped spawn — returns { status: number }. stdio inherited so the
551
576
  // user sees pnpm's output. Used for self-update + skills refresh.
552
577
  function spawnSync(cmd, args) {
@@ -923,6 +948,7 @@ async function mailSend(rest) {
923
948
  subject,
924
949
  response: result,
925
950
  })
951
+ noteAutoLog(action, opts)
926
952
  printJson({ ...result, autark_action_id: action?.id })
927
953
  }
928
954
 
@@ -940,6 +966,7 @@ async function mailReply(rest, mode) {
940
966
  response: result,
941
967
  metadata: { message_id: messageId },
942
968
  })
969
+ noteAutoLog(action, opts)
943
970
  printJson({ ...result, autark_action_id: action?.id })
944
971
  }
945
972
 
@@ -957,9 +984,20 @@ async function mailForward(rest) {
957
984
  response: result,
958
985
  metadata: { message_id: messageId },
959
986
  })
987
+ noteAutoLog(action, opts)
960
988
  printJson({ ...result, autark_action_id: action?.id })
961
989
  }
962
990
 
991
+ // Explicit human-readable cue so agents (and humans) see that the action
992
+ // landed in autark and don't follow up with a redundant `autark log action`
993
+ // for the same send. Stays on stderr so JSON consumers piping stdout aren't
994
+ // affected. Only fires when --run-id was passed and the log succeeded.
995
+ function noteAutoLog(action, opts) {
996
+ if (!action?.id) return
997
+ const runId = opts['run-id'] || opts.run_id || opts.run
998
+ console.error(`autark: auto-logged action ${action.id} (run ${runId}) — do NOT run \`autark log action\` for this`)
999
+ }
1000
+
963
1001
  async function mailThreads(rest) {
964
1002
  const opts = parseArgs(rest)
965
1003
  const creds = requireAgentmailCredentials()
@@ -1124,6 +1162,21 @@ function actionShortId(id) {
1124
1162
  return String(id || '').slice(0, 8)
1125
1163
  }
1126
1164
 
1165
+ // Aggregate sends + replies per channel. The cron writes inbound_count onto
1166
+ // each action; agents use this to score hypotheses without re-walking threads.
1167
+ // Channels are kept separate (email vs github vs reddit vs hn vs substack) —
1168
+ // a public comment reply is a different signal class than an email reply.
1169
+ function channelBreakdown(actions) {
1170
+ const by = {}
1171
+ for (const a of (actions || [])) {
1172
+ const ch = a.channel || 'unknown'
1173
+ if (!by[ch]) by[ch] = { sent: 0, replied: 0 }
1174
+ by[ch].sent += 1
1175
+ if ((a.inbound_count || 0) > 0) by[ch].replied += 1
1176
+ }
1177
+ return by
1178
+ }
1179
+
1127
1180
  function printProductContext(r) {
1128
1181
  // ---- structured key-block ----
1129
1182
  kv('product.id', r.product.id)
@@ -1138,6 +1191,15 @@ function printProductContext(r) {
1138
1191
  kv(`hypothesis.${h.code}.title`, h.title)
1139
1192
  kv(`hypothesis.${h.code}.status`, h.status)
1140
1193
  kv(`hypothesis.${h.code}.run_count`, h.run_count)
1194
+ kv(`hypothesis.${h.code}.action_count`, h.action_count)
1195
+ kv(`hypothesis.${h.code}.reply_count`, h.reply_count)
1196
+ if (h.needs_human_count) kv(`hypothesis.${h.code}.needs_human_count`, h.needs_human_count)
1197
+ const actions = (h.runs || []).flatMap((run) => run.actions || [])
1198
+ const by = channelBreakdown(actions)
1199
+ for (const ch of Object.keys(by).sort()) {
1200
+ kv(`hypothesis.${h.code}.channel.${ch}.sent`, by[ch].sent)
1201
+ kv(`hypothesis.${h.code}.channel.${ch}.replied`, by[ch].replied)
1202
+ }
1141
1203
  }
1142
1204
  console.log('---')
1143
1205
  // ---- narrative ----
@@ -1150,7 +1212,13 @@ function printProductContext(r) {
1150
1212
  } else {
1151
1213
  console.log(`\n## Hypotheses (${r.hypotheses.length})\n`)
1152
1214
  for (const h of r.hypotheses) {
1153
- console.log(`- [${h.status}] ${h.code} ${h.title} (runs: ${h.run_count})`)
1215
+ const actions = (h.runs || []).flatMap((run) => run.actions || [])
1216
+ const by = channelBreakdown(actions)
1217
+ const channelSummary = Object.keys(by).sort()
1218
+ .map((ch) => `${ch} ${by[ch].replied}/${by[ch].sent}`)
1219
+ .join(' ')
1220
+ const tail = channelSummary ? ` — replies/sends by channel: ${channelSummary}` : ''
1221
+ console.log(`- [${h.status}] ${h.code} — ${h.title} (runs: ${h.run_count}, actions: ${h.action_count || 0}, replies: ${h.reply_count || 0})${tail}`)
1154
1222
  }
1155
1223
  }
1156
1224
  }
@@ -1173,6 +1241,17 @@ function printHypothesisContext(result) {
1173
1241
  if (f.action_id) kv(`feedback.${shortF}.action_id`, f.action_id)
1174
1242
  kv(`feedback.${shortF}.text`, f.text)
1175
1243
  }
1244
+ // Per-channel rollup across all runs on this hypothesis. Lets the agent
1245
+ // grep `hypothesis.channel.*` to see what's actually landing without
1246
+ // counting actions itself.
1247
+ const allActions = (result.runs || []).flatMap((run) => run.actions || [])
1248
+ kv('hypothesis.action_count', allActions.length)
1249
+ kv('hypothesis.reply_count', allActions.filter((a) => (a.inbound_count || 0) > 0).length)
1250
+ const channelTotals = channelBreakdown(allActions)
1251
+ for (const ch of Object.keys(channelTotals).sort()) {
1252
+ kv(`hypothesis.channel.${ch}.sent`, channelTotals[ch].sent)
1253
+ kv(`hypothesis.channel.${ch}.replied`, channelTotals[ch].replied)
1254
+ }
1176
1255
  for (const run of (result.runs || [])) {
1177
1256
  const shortRun = actionShortId(run.id)
1178
1257
  kv(`run.${shortRun}.id`, run.id)
@@ -1190,6 +1269,11 @@ function printHypothesisContext(result) {
1190
1269
  if (a.agentmail_thread_id) kv(`action.${shortA}.agentmail_thread_id`, a.agentmail_thread_id)
1191
1270
  if (a.agentmail_inbox_id) kv(`action.${shortA}.agentmail_inbox_id`, a.agentmail_inbox_id)
1192
1271
  if (a.occurred_at) kv(`action.${shortA}.occurred_at`, a.occurred_at)
1272
+ kv(`action.${shortA}.inbound_count`, a.inbound_count || 0)
1273
+ if (a.last_inbound_at) kv(`action.${shortA}.last_inbound_at`, a.last_inbound_at)
1274
+ if (a.reply_checked_at) kv(`action.${shortA}.reply_checked_at`, a.reply_checked_at)
1275
+ if (a.needs_human) kv(`action.${shortA}.needs_human`, a.needs_human)
1276
+ if (a.escalation_reason) kv(`action.${shortA}.escalation_reason`, a.escalation_reason)
1193
1277
  // Spread well-known metadata keys to their own lines for greppability.
1194
1278
  const md = a.metadata || {}
1195
1279
  for (const [mk, mv] of Object.entries(md)) {
@@ -1218,7 +1302,11 @@ function printHypothesisContext(result) {
1218
1302
  console.log(`\nActions:`)
1219
1303
  for (const a of run.actions) {
1220
1304
  const pointer = a.url || a.agentmail_thread_id || a.recipient || ''
1221
- console.log(` [${a.channel}] ${a.title}${pointer ? ` → ${pointer}` : ''}`)
1305
+ const flags = []
1306
+ if ((a.inbound_count || 0) > 0) flags.push(`replied×${a.inbound_count}`)
1307
+ if (a.needs_human) flags.push('needs_human')
1308
+ const flagStr = flags.length ? ` [${flags.join(', ')}]` : ''
1309
+ console.log(` [${a.channel}] ${a.title}${pointer ? ` → ${pointer}` : ''}${flagStr}`)
1222
1310
  }
1223
1311
  }
1224
1312
  if (run.narrative_md) console.log(`\n${run.narrative_md}`)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "autark-cli",
3
- "version": "0.5.2",
3
+ "version": "0.5.3",
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",