autark-cli 0.1.4 → 0.1.5
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 +27 -21
- package/autark.mjs +298 -37
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -18,47 +18,53 @@ autark login send your@email.com
|
|
|
18
18
|
autark login verify your@email.com --code 123456
|
|
19
19
|
```
|
|
20
20
|
|
|
21
|
-
##
|
|
21
|
+
## Runbook use
|
|
22
22
|
|
|
23
23
|
Autark is ID-first. Use `product list` / `context` to discover IDs, then pass IDs for writes. `slug/H01` remains a convenience alias.
|
|
24
24
|
|
|
25
25
|
```sh
|
|
26
|
-
# products
|
|
27
26
|
autark product upsert --slug chrome-relay --name "Chrome Relay" --tagline "..."
|
|
28
27
|
autark product list # prints slug, visibility, id, name
|
|
29
28
|
autark context chrome-relay
|
|
30
29
|
|
|
31
|
-
autark context --product-id <product_id>
|
|
32
|
-
|
|
33
|
-
# hypotheses
|
|
34
30
|
autark hypothesis create --product-id <product_id> --md @./H01.md --code H01 --title "..."
|
|
35
|
-
autark context --hypothesis-id <hypothesis_id>
|
|
36
|
-
autark hypothesis status --hypothesis-id <hypothesis_id> --status active # active|inactive|dead
|
|
37
|
-
|
|
38
|
-
# aliases still work
|
|
39
|
-
autark hypothesis create --product chrome-relay --md @./H01.md --code H01
|
|
40
|
-
autark context chrome-relay/H01
|
|
41
|
-
autark hypothesis status chrome-relay/H01 --status inactive
|
|
42
|
-
|
|
43
|
-
# runs
|
|
44
31
|
autark run start --hypothesis-id <hypothesis_id>
|
|
32
|
+
autark log action --run-id <run_id> --channel github --title "..." --url https://github.com/...
|
|
45
33
|
autark run finish --run-id <run_id> --narrative @./narrative.md
|
|
34
|
+
```
|
|
46
35
|
|
|
47
|
-
|
|
48
|
-
autark log action --run-id <run_id> --channel email --title "..." \
|
|
49
|
-
--recipient person@example.com --agentmail-thread-id <thread_id>
|
|
50
|
-
autark log action --run-id <run_id> --channel github --title "..." --url https://github.com/...
|
|
36
|
+
## Mail use
|
|
51
37
|
|
|
52
|
-
autark
|
|
53
|
-
|
|
38
|
+
`autark mail` covers the AgentMail surface Autark needs. It does not require a separate AgentMail CLI login.
|
|
39
|
+
|
|
40
|
+
```sh
|
|
41
|
+
autark mail setup --prefix laksh
|
|
42
|
+
|
|
43
|
+
autark mail send --run-id <run_id> \
|
|
44
|
+
--to person@example.com \
|
|
45
|
+
--subject "Subject" \
|
|
46
|
+
--text @./body.txt
|
|
47
|
+
|
|
48
|
+
autark mail reply --run-id <run_id> --message-id <message_id> --text @./reply.txt
|
|
49
|
+
|
|
50
|
+
autark mail threads --limit 20
|
|
51
|
+
autark mail thread <thread_id>
|
|
52
|
+
autark mail messages --limit 20
|
|
53
|
+
autark mail message <message_id>
|
|
54
|
+
autark mail raw <message_id>
|
|
55
|
+
autark mail attachment --message-id <message_id> --attachment-id <attachment_id> --out file.bin
|
|
54
56
|
```
|
|
55
57
|
|
|
58
|
+
Mail sends/replies call AgentMail directly with the user's inbox-scoped key from `~/.autark/credentials.json`, then log an Autark `email` action containing `agentmail_thread_id` and `agentmail_inbox_id`.
|
|
59
|
+
|
|
56
60
|
## ENV
|
|
57
61
|
|
|
58
62
|
- `AUTARK_API_URL` — override the default Worker URL (`https://autark-api.kushalsokke.workers.dev`)
|
|
63
|
+
- `AGENTMAIL_API_URL` — override AgentMail API base
|
|
64
|
+
- `AGENTMAIL_API_KEY`, `AGENTMAIL_EMAIL`, `AGENTMAIL_INBOX_ID` — override local mail credentials for debugging
|
|
59
65
|
|
|
60
66
|
## Architecture
|
|
61
67
|
|
|
62
68
|
The CLI is a thin HTTP client over a Cloudflare Worker that holds the InstantDB admin token. Magic-link auth via InstantDB. Token saved to `~/.autark/credentials.json` after login.
|
|
63
69
|
|
|
64
|
-
The
|
|
70
|
+
The Worker uses the AgentMail org key only for provisioning and dashboard thread reads. Local mail send/reply uses the user's inbox-scoped key.
|
package/autark.mjs
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// autark CLI — thin HTTP client over the autark-api Worker.
|
|
3
3
|
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
// The CLI is ID-first: once a product/hypothesis/run exists, use its id.
|
|
8
|
-
// slug/Hxx remains as a convenience alias for humans.
|
|
4
|
+
// The runbook API is ID-first. slug/Hxx remains as a convenience alias.
|
|
5
|
+
// Mail commands call AgentMail directly with the user's inbox-scoped key;
|
|
6
|
+
// autark-api uses the org key only for provisioning and dashboard thread reads.
|
|
9
7
|
|
|
10
8
|
import fs from 'node:fs'
|
|
11
9
|
import os from 'node:os'
|
|
@@ -13,6 +11,7 @@ import path from 'node:path'
|
|
|
13
11
|
import process from 'node:process'
|
|
14
12
|
|
|
15
13
|
const API = process.env.AUTARK_API_URL || 'https://autark-api.kushalsokke.workers.dev'
|
|
14
|
+
const AGENTMAIL_API = process.env.AGENTMAIL_API_URL || 'https://api.agentmail.to/v0'
|
|
16
15
|
const CREDS_PATH = path.join(os.homedir(), '.autark', 'credentials.json')
|
|
17
16
|
|
|
18
17
|
const args = process.argv.slice(2)
|
|
@@ -47,6 +46,7 @@ async function main() {
|
|
|
47
46
|
}
|
|
48
47
|
if (group === 'log' && command === 'action') return logAction(rest)
|
|
49
48
|
if (group === 'context') return context([command, ...rest].filter(Boolean))
|
|
49
|
+
if (group === 'mail') return mail(command, rest)
|
|
50
50
|
|
|
51
51
|
usage()
|
|
52
52
|
process.exit(1)
|
|
@@ -69,6 +69,7 @@ async function loginVerify(rest) {
|
|
|
69
69
|
const result = await api('POST', '/v1/auth/login/verify', { email, code }, { auth: false })
|
|
70
70
|
if (!result.token) throw new Error('verify succeeded but no token returned')
|
|
71
71
|
saveCredentials({
|
|
72
|
+
...loadCredentials(),
|
|
72
73
|
user_id: result.user.id,
|
|
73
74
|
email: result.user.email,
|
|
74
75
|
token: result.token,
|
|
@@ -194,15 +195,235 @@ async function logAction(rest) {
|
|
|
194
195
|
const result = await api('POST', `/v1/runs/${encodeURIComponent(run)}/actions`, {
|
|
195
196
|
channel,
|
|
196
197
|
title,
|
|
197
|
-
url:
|
|
198
|
-
agentmail_thread_id:
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
198
|
+
url: opts.url || undefined,
|
|
199
|
+
agentmail_thread_id: opts['agentmail-thread-id'] || opts['thread-id'] || opts.thread_id || undefined,
|
|
200
|
+
agentmail_inbox_id: opts['agentmail-inbox-id'] || opts.inbox_id || opts.agentmail_inbox_id || undefined,
|
|
201
|
+
recipient: opts.recipient || undefined,
|
|
202
|
+
metadata: opts.metadata ? JSON.parse(readValue(opts.metadata)) : undefined,
|
|
203
|
+
occurred_at: opts['occurred-at'] || opts.occurred_at || undefined,
|
|
202
204
|
})
|
|
203
205
|
console.log(result.id)
|
|
204
206
|
}
|
|
205
207
|
|
|
208
|
+
// ============================================================ mail
|
|
209
|
+
|
|
210
|
+
async function mail(command, rest) {
|
|
211
|
+
if (!command || command === '--help' || command === 'help') return mailUsage()
|
|
212
|
+
if (command === 'setup') return mailSetup(rest)
|
|
213
|
+
if (command === 'send') return mailSend(rest)
|
|
214
|
+
if (command === 'reply') return mailReply(rest, 'reply')
|
|
215
|
+
if (command === 'reply-all') return mailReply(rest, 'reply-all')
|
|
216
|
+
if (command === 'forward') return mailForward(rest)
|
|
217
|
+
if (command === 'threads') return mailThreads(rest)
|
|
218
|
+
if (command === 'thread') return mailThread(rest)
|
|
219
|
+
if (command === 'messages') return mailMessages(rest)
|
|
220
|
+
if (command === 'message') return mailMessage(rest)
|
|
221
|
+
if (command === 'raw') return mailRaw(rest)
|
|
222
|
+
if (command === 'attachment') return mailAttachment(rest)
|
|
223
|
+
if (command === 'request') return mailRequest(rest)
|
|
224
|
+
mailUsage()
|
|
225
|
+
process.exit(1)
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
async function mailSetup(rest) {
|
|
229
|
+
const opts = parseArgs(rest)
|
|
230
|
+
const existing = loadCredentials() || {}
|
|
231
|
+
if (existing.agentmail_token && existing.agentmail_email && !opts.force) {
|
|
232
|
+
console.log(existing.agentmail_email)
|
|
233
|
+
return
|
|
234
|
+
}
|
|
235
|
+
const prefix = required(opts.prefix || defaultInboxPrefix(existing.email), '--prefix')
|
|
236
|
+
const result = await api('POST', '/v1/onboard/agentmail', {
|
|
237
|
+
prefix,
|
|
238
|
+
display_name: opts['display-name'] || opts.display_name || existing.email || prefix,
|
|
239
|
+
purpose: opts.purpose || 'runner',
|
|
240
|
+
})
|
|
241
|
+
saveCredentials({
|
|
242
|
+
...existing,
|
|
243
|
+
agentmail_email: result.agentmail_email || result.email,
|
|
244
|
+
agentmail_inbox_id: result.agentmail_inbox_id || result.inbox_id || result.email,
|
|
245
|
+
agentmail_api_key_id: result.agentmail_api_key_id || result.api_key_id,
|
|
246
|
+
agentmail_token: result.agentmail_token,
|
|
247
|
+
agentmail_saved_at: new Date().toISOString(),
|
|
248
|
+
})
|
|
249
|
+
console.log(result.agentmail_email || result.email)
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
async function mailSend(rest) {
|
|
253
|
+
const opts = parseArgs(rest)
|
|
254
|
+
const creds = requireAgentmailCredentials()
|
|
255
|
+
const to = listOpt(opts.to, '--to')
|
|
256
|
+
const subject = opts.subject || ''
|
|
257
|
+
const body = mailBody(opts, { to, subject })
|
|
258
|
+
const result = await agentmailRequest('POST', `/inboxes/${encodeURIComponent(creds.inboxId)}/messages/send`, body)
|
|
259
|
+
const action = await maybeLogMailAction(opts, {
|
|
260
|
+
kind: 'send',
|
|
261
|
+
defaultTitle: opts.title || `Email: ${subject || to.join(', ')}`,
|
|
262
|
+
recipient: to.join(','),
|
|
263
|
+
subject,
|
|
264
|
+
response: result,
|
|
265
|
+
})
|
|
266
|
+
printJson({ ...result, autark_action_id: action?.id })
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
async function mailReply(rest, mode) {
|
|
270
|
+
const opts = parseArgs(rest)
|
|
271
|
+
const creds = requireAgentmailCredentials()
|
|
272
|
+
const messageId = required(opts['message-id'] || opts.message_id || opts._[0], '--message-id')
|
|
273
|
+
const body = mailBody(opts)
|
|
274
|
+
const endpoint = mode === 'reply-all' ? 'reply-all' : 'reply'
|
|
275
|
+
const result = await agentmailRequest('POST', `/inboxes/${encodeURIComponent(creds.inboxId)}/messages/${encodeURIComponent(messageId)}/${endpoint}`, body)
|
|
276
|
+
const action = await maybeLogMailAction(opts, {
|
|
277
|
+
kind: mode,
|
|
278
|
+
defaultTitle: opts.title || `${mode === 'reply-all' ? 'Reply-all' : 'Reply'}: ${messageId}`,
|
|
279
|
+
recipient: listOpt(opts.to).join(','),
|
|
280
|
+
response: result,
|
|
281
|
+
metadata: { message_id: messageId },
|
|
282
|
+
})
|
|
283
|
+
printJson({ ...result, autark_action_id: action?.id })
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
async function mailForward(rest) {
|
|
287
|
+
const opts = parseArgs(rest)
|
|
288
|
+
const creds = requireAgentmailCredentials()
|
|
289
|
+
const messageId = required(opts['message-id'] || opts.message_id || opts._[0], '--message-id')
|
|
290
|
+
const to = listOpt(opts.to, '--to')
|
|
291
|
+
const body = mailBody(opts, { to, subject: opts.subject || '' })
|
|
292
|
+
const result = await agentmailRequest('POST', `/inboxes/${encodeURIComponent(creds.inboxId)}/messages/${encodeURIComponent(messageId)}/forward`, body)
|
|
293
|
+
const action = await maybeLogMailAction(opts, {
|
|
294
|
+
kind: 'forward',
|
|
295
|
+
defaultTitle: opts.title || `Forward: ${opts.subject || messageId}`,
|
|
296
|
+
recipient: to.join(','),
|
|
297
|
+
response: result,
|
|
298
|
+
metadata: { message_id: messageId },
|
|
299
|
+
})
|
|
300
|
+
printJson({ ...result, autark_action_id: action?.id })
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
async function mailThreads(rest) {
|
|
304
|
+
const opts = parseArgs(rest)
|
|
305
|
+
const creds = requireAgentmailCredentials()
|
|
306
|
+
const qs = queryString({ limit: opts.limit, page_token: opts['page-token'] || opts.page_token })
|
|
307
|
+
printJson(await agentmailRequest('GET', `/inboxes/${encodeURIComponent(creds.inboxId)}/threads${qs}`))
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
async function mailThread(rest) {
|
|
311
|
+
const opts = parseArgs(rest)
|
|
312
|
+
const creds = requireAgentmailCredentials()
|
|
313
|
+
const threadId = required(opts['thread-id'] || opts.thread_id || opts._[0], 'thread_id')
|
|
314
|
+
printJson(await agentmailRequest('GET', `/inboxes/${encodeURIComponent(creds.inboxId)}/threads/${encodeURIComponent(threadId)}`))
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
async function mailMessages(rest) {
|
|
318
|
+
const opts = parseArgs(rest)
|
|
319
|
+
const creds = requireAgentmailCredentials()
|
|
320
|
+
const qs = queryString({ limit: opts.limit, page_token: opts['page-token'] || opts.page_token })
|
|
321
|
+
printJson(await agentmailRequest('GET', `/inboxes/${encodeURIComponent(creds.inboxId)}/messages${qs}`))
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
async function mailMessage(rest) {
|
|
325
|
+
const opts = parseArgs(rest)
|
|
326
|
+
const creds = requireAgentmailCredentials()
|
|
327
|
+
const messageId = required(opts['message-id'] || opts.message_id || opts._[0], 'message_id')
|
|
328
|
+
printJson(await agentmailRequest('GET', `/inboxes/${encodeURIComponent(creds.inboxId)}/messages/${encodeURIComponent(messageId)}`))
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
async function mailRaw(rest) {
|
|
332
|
+
const opts = parseArgs(rest)
|
|
333
|
+
const creds = requireAgentmailCredentials()
|
|
334
|
+
const messageId = required(opts['message-id'] || opts.message_id || opts._[0], 'message_id')
|
|
335
|
+
const raw = await agentmailRequest('GET', `/inboxes/${encodeURIComponent(creds.inboxId)}/messages/${encodeURIComponent(messageId)}/raw`, undefined, { parseJson: false })
|
|
336
|
+
process.stdout.write(raw)
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
async function mailAttachment(rest) {
|
|
340
|
+
const opts = parseArgs(rest)
|
|
341
|
+
const creds = requireAgentmailCredentials()
|
|
342
|
+
const messageId = required(opts['message-id'] || opts.message_id, '--message-id')
|
|
343
|
+
const attachmentId = required(opts['attachment-id'] || opts.attachment_id, '--attachment-id')
|
|
344
|
+
const body = await agentmailRequest('GET', `/inboxes/${encodeURIComponent(creds.inboxId)}/messages/${encodeURIComponent(messageId)}/attachments/${encodeURIComponent(attachmentId)}`, undefined, { parseJson: false })
|
|
345
|
+
if (opts.out) fs.writeFileSync(opts.out, body)
|
|
346
|
+
else process.stdout.write(body)
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
async function mailRequest(rest) {
|
|
350
|
+
const opts = parseArgs(rest)
|
|
351
|
+
const method = (opts.method || opts._[0] || 'GET').toUpperCase()
|
|
352
|
+
const requestPath = required(opts.path || opts._[1], '--path')
|
|
353
|
+
const body = opts.body ? JSON.parse(readValue(opts.body)) : undefined
|
|
354
|
+
const parseJson = opts.raw ? false : true
|
|
355
|
+
const result = await agentmailRequest(method, requestPath.startsWith('/') ? requestPath : `/${requestPath}`, body, { parseJson })
|
|
356
|
+
if (parseJson) printJson(result)
|
|
357
|
+
else process.stdout.write(result)
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
function mailBody(opts, base = {}) {
|
|
361
|
+
const body = { ...base }
|
|
362
|
+
if (opts.text !== undefined) body.text = readValue(opts.text)
|
|
363
|
+
if (opts.html !== undefined) body.html = readValue(opts.html)
|
|
364
|
+
if (opts.subject !== undefined) body.subject = opts.subject
|
|
365
|
+
for (const key of ['cc', 'bcc', 'reply-to', 'label', 'attachment']) {
|
|
366
|
+
const snake = key.replaceAll('-', '_')
|
|
367
|
+
const value = opts[key] ?? opts[snake]
|
|
368
|
+
if (value !== undefined) body[snake] = listOpt(value)
|
|
369
|
+
}
|
|
370
|
+
if (opts.headers !== undefined) body.headers = JSON.parse(readValue(opts.headers))
|
|
371
|
+
return body
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
async function maybeLogMailAction(opts, { kind, defaultTitle, recipient, subject, response, metadata = {} }) {
|
|
375
|
+
const runId = opts['run-id'] || opts.run_id || opts.run
|
|
376
|
+
if (!runId) return null
|
|
377
|
+
const creds = requireAgentmailCredentials()
|
|
378
|
+
const threadId = response?.thread_id
|
|
379
|
+
if (!threadId) throw new Error('AgentMail response missing thread_id; refusing to log email action')
|
|
380
|
+
return api('POST', `/v1/runs/${encodeURIComponent(runId)}/actions`, {
|
|
381
|
+
channel: 'email',
|
|
382
|
+
title: opts.title || defaultTitle,
|
|
383
|
+
recipient: recipient || undefined,
|
|
384
|
+
agentmail_thread_id: threadId,
|
|
385
|
+
agentmail_inbox_id: creds.inboxId,
|
|
386
|
+
metadata: compact({
|
|
387
|
+
kind,
|
|
388
|
+
subject,
|
|
389
|
+
message_id: response?.message_id,
|
|
390
|
+
...metadata,
|
|
391
|
+
}),
|
|
392
|
+
})
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
async function agentmailRequest(method, pathStr, body, { parseJson = true } = {}) {
|
|
396
|
+
const creds = requireAgentmailCredentials()
|
|
397
|
+
const res = await fetch(AGENTMAIL_API + pathStr, {
|
|
398
|
+
method,
|
|
399
|
+
headers: {
|
|
400
|
+
authorization: `Bearer ${creds.token}`,
|
|
401
|
+
'content-type': 'application/json',
|
|
402
|
+
},
|
|
403
|
+
body: body === undefined ? undefined : JSON.stringify(body),
|
|
404
|
+
})
|
|
405
|
+
const text = await res.text()
|
|
406
|
+
let data = text
|
|
407
|
+
if (parseJson) {
|
|
408
|
+
try { data = text ? JSON.parse(text) : null } catch { data = { raw: text } }
|
|
409
|
+
}
|
|
410
|
+
if (!res.ok) {
|
|
411
|
+
const msg = parseJson ? (data?.error || data?.message || JSON.stringify(data)) : text
|
|
412
|
+
throw new Error(`AgentMail ${res.status}: ${msg}`)
|
|
413
|
+
}
|
|
414
|
+
return data
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
function requireAgentmailCredentials() {
|
|
418
|
+
const creds = loadCredentials()
|
|
419
|
+
const token = process.env.AGENTMAIL_API_KEY || creds?.agentmail_token
|
|
420
|
+
const email = process.env.AGENTMAIL_EMAIL || creds?.agentmail_email
|
|
421
|
+
const inboxId = process.env.AGENTMAIL_INBOX_ID || creds?.agentmail_inbox_id || email
|
|
422
|
+
if (!token) throw new Error('missing agentmail_token. run: autark mail setup --prefix <name>')
|
|
423
|
+
if (!inboxId) throw new Error('missing agentmail_inbox_id. run: autark mail setup --prefix <name>')
|
|
424
|
+
return { token, email, inboxId }
|
|
425
|
+
}
|
|
426
|
+
|
|
206
427
|
// ============================================================ context
|
|
207
428
|
|
|
208
429
|
async function context(rest) {
|
|
@@ -223,12 +444,8 @@ async function context(rest) {
|
|
|
223
444
|
const productSlug = parts[0]
|
|
224
445
|
const code = parts[1]
|
|
225
446
|
|
|
226
|
-
if (!code) {
|
|
227
|
-
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
const result = await api('GET', `/v1/context/${encodeURIComponent(productSlug)}/${encodeURIComponent(code)}`)
|
|
231
|
-
return printHypothesisContext(result)
|
|
447
|
+
if (!code) return printProductContext(await api('GET', `/v1/context/${encodeURIComponent(productSlug)}`))
|
|
448
|
+
return printHypothesisContext(await api('GET', `/v1/context/${encodeURIComponent(productSlug)}/${encodeURIComponent(code)}`))
|
|
232
449
|
}
|
|
233
450
|
|
|
234
451
|
function printProductContext(r) {
|
|
@@ -264,9 +481,7 @@ function printHypothesisContext(result) {
|
|
|
264
481
|
console.log(` [${a.channel}] ${a.title}${pointer ? ` → ${pointer}` : ''}`)
|
|
265
482
|
}
|
|
266
483
|
}
|
|
267
|
-
if (run.narrative_md) {
|
|
268
|
-
console.log(`\n${run.narrative_md}`)
|
|
269
|
-
}
|
|
484
|
+
if (run.narrative_md) console.log(`\n${run.narrative_md}`)
|
|
270
485
|
}
|
|
271
486
|
}
|
|
272
487
|
|
|
@@ -301,14 +516,10 @@ function saveCredentials(creds) {
|
|
|
301
516
|
|
|
302
517
|
function loadCredentials() {
|
|
303
518
|
if (!fs.existsSync(CREDS_PATH)) return null
|
|
304
|
-
try {
|
|
305
|
-
return JSON.parse(fs.readFileSync(CREDS_PATH, 'utf8'))
|
|
306
|
-
} catch {
|
|
307
|
-
return null
|
|
308
|
-
}
|
|
519
|
+
try { return JSON.parse(fs.readFileSync(CREDS_PATH, 'utf8')) } catch { return null }
|
|
309
520
|
}
|
|
310
521
|
|
|
311
|
-
// ============================================================
|
|
522
|
+
// ============================================================ utilities
|
|
312
523
|
|
|
313
524
|
function parseArgs(list) {
|
|
314
525
|
const opts = { _: [] }
|
|
@@ -320,12 +531,11 @@ function parseArgs(list) {
|
|
|
320
531
|
}
|
|
321
532
|
const key = arg.slice(2)
|
|
322
533
|
const next = list[i + 1]
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
}
|
|
534
|
+
const value = (!next || next.startsWith('--')) ? true : next
|
|
535
|
+
if (value !== true) i++
|
|
536
|
+
if (opts[key] === undefined) opts[key] = value
|
|
537
|
+
else if (Array.isArray(opts[key])) opts[key].push(value)
|
|
538
|
+
else opts[key] = [opts[key], value]
|
|
329
539
|
}
|
|
330
540
|
return opts
|
|
331
541
|
}
|
|
@@ -338,12 +548,43 @@ function required(value, label) {
|
|
|
338
548
|
}
|
|
339
549
|
|
|
340
550
|
function readValue(value) {
|
|
341
|
-
if (typeof value === 'string' && value.startsWith('@'))
|
|
342
|
-
return fs.readFileSync(value.slice(1), 'utf8')
|
|
343
|
-
}
|
|
551
|
+
if (typeof value === 'string' && value.startsWith('@')) return fs.readFileSync(value.slice(1), 'utf8')
|
|
344
552
|
return String(value)
|
|
345
553
|
}
|
|
346
554
|
|
|
555
|
+
function listOpt(value, label) {
|
|
556
|
+
if (value === undefined || value === null || value === '') {
|
|
557
|
+
if (label) throw new Error(`${label} is required`)
|
|
558
|
+
return []
|
|
559
|
+
}
|
|
560
|
+
const values = Array.isArray(value) ? value : [value]
|
|
561
|
+
return values.flatMap(v => String(v).split(',')).map(v => v.trim()).filter(Boolean)
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
function queryString(params) {
|
|
565
|
+
const q = new URLSearchParams()
|
|
566
|
+
for (const [k, v] of Object.entries(params)) if (v !== undefined && v !== null && v !== '') q.set(k, v)
|
|
567
|
+
const s = q.toString()
|
|
568
|
+
return s ? `?${s}` : ''
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
function defaultInboxPrefix(email) {
|
|
572
|
+
if (!email) return ''
|
|
573
|
+
return String(email).split('@')[0].toLowerCase().replace(/[^a-z0-9._-]/g, '').replace(/^[._-]+/, '').slice(0, 30)
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
function compact(obj) {
|
|
577
|
+
const out = {}
|
|
578
|
+
for (const [k, v] of Object.entries(obj || {})) {
|
|
579
|
+
if (v !== undefined && v !== null && v !== '' && !(Array.isArray(v) && !v.length)) out[k] = v
|
|
580
|
+
}
|
|
581
|
+
return out
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
function printJson(value) {
|
|
585
|
+
console.log(JSON.stringify(value, null, 2))
|
|
586
|
+
}
|
|
587
|
+
|
|
347
588
|
function splitHypothesisRef(ref) {
|
|
348
589
|
const m = String(ref).match(/^([\w.-]+)\/(H\d{2})$/)
|
|
349
590
|
if (!m) throw new Error(`expected product/Hxx, got ${ref}`)
|
|
@@ -353,8 +594,26 @@ function splitHypothesisRef(ref) {
|
|
|
353
594
|
// ============================================================ usage
|
|
354
595
|
|
|
355
596
|
function loginUsage() {
|
|
356
|
-
console.log(`autark login send <email>
|
|
357
|
-
|
|
597
|
+
console.log(`autark login send <email>\nautark login verify <email> --code <6-digit-code>`)
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
function mailUsage() {
|
|
601
|
+
console.log(`autark mail
|
|
602
|
+
|
|
603
|
+
setup --prefix <name> [--force]
|
|
604
|
+
send --to <email[,email]> --subject <s> --text @body.txt [--run-id <id>]
|
|
605
|
+
reply --message-id <id> --text @reply.txt [--run-id <id>]
|
|
606
|
+
reply-all --message-id <id> --text @reply.txt [--run-id <id>]
|
|
607
|
+
forward --message-id <id> --to <email> [--text @body.txt] [--run-id <id>]
|
|
608
|
+
threads [--limit N]
|
|
609
|
+
thread <thread_id>
|
|
610
|
+
messages [--limit N]
|
|
611
|
+
message <message_id>
|
|
612
|
+
raw <message_id>
|
|
613
|
+
attachment --message-id <id> --attachment-id <id> [--out file]
|
|
614
|
+
request <METHOD> <path> [--body @json] [--raw]
|
|
615
|
+
|
|
616
|
+
Mail uses the inbox-scoped token in ~/.autark/credentials.json.`)
|
|
358
617
|
}
|
|
359
618
|
|
|
360
619
|
function usage() {
|
|
@@ -378,9 +637,11 @@ function usage() {
|
|
|
378
637
|
autark run finish --run-id <id> --narrative @./run.md
|
|
379
638
|
|
|
380
639
|
autark log action --run-id <id> --channel <c> --title <t> [--url <u>]
|
|
381
|
-
[--agentmail-thread-id <uuid>] [--recipient <email>]
|
|
640
|
+
[--agentmail-thread-id <uuid>] [--agentmail-inbox-id <inbox>] [--recipient <email>]
|
|
382
641
|
[--metadata @./meta.json]
|
|
383
642
|
|
|
643
|
+
autark mail setup/send/reply/thread/messages/... AgentMail via autark credentials
|
|
644
|
+
|
|
384
645
|
autark context --product-id <id>
|
|
385
646
|
autark context --hypothesis-id <id>
|
|
386
647
|
autark context <slug>
|
package/package.json
CHANGED