autark-cli 0.1.4 → 0.1.6
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 +377 -58
- 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)
|
|
@@ -36,17 +35,27 @@ async function main() {
|
|
|
36
35
|
if (group === 'product') {
|
|
37
36
|
if (command === 'upsert') return productUpsert(rest)
|
|
38
37
|
if (command === 'list') return productList()
|
|
38
|
+
if (!command || command === '--help' || command === '-h') return productUsage()
|
|
39
39
|
}
|
|
40
40
|
if (group === 'hypothesis') {
|
|
41
41
|
if (command === 'create') return hypothesisCreate(rest)
|
|
42
42
|
if (command === 'status') return hypothesisStatus(rest)
|
|
43
|
+
if (!command || command === '--help' || command === '-h') return hypothesisUsage()
|
|
43
44
|
}
|
|
44
45
|
if (group === 'run') {
|
|
45
46
|
if (command === 'start') return runStart(rest)
|
|
46
47
|
if (command === 'finish') return runFinish(rest)
|
|
48
|
+
if (!command || command === '--help' || command === '-h') return runUsage()
|
|
49
|
+
}
|
|
50
|
+
if (group === 'log') {
|
|
51
|
+
if (command === 'action') return logAction(rest)
|
|
52
|
+
if (!command || command === '--help' || command === '-h') return logUsage()
|
|
47
53
|
}
|
|
48
|
-
if (group === '
|
|
49
|
-
|
|
54
|
+
if (group === 'context') {
|
|
55
|
+
if (!command || command === '--help' || command === '-h') return contextUsage()
|
|
56
|
+
return context([command, ...rest].filter(Boolean))
|
|
57
|
+
}
|
|
58
|
+
if (group === 'mail') return mail(command, rest)
|
|
50
59
|
|
|
51
60
|
usage()
|
|
52
61
|
process.exit(1)
|
|
@@ -69,6 +78,7 @@ async function loginVerify(rest) {
|
|
|
69
78
|
const result = await api('POST', '/v1/auth/login/verify', { email, code }, { auth: false })
|
|
70
79
|
if (!result.token) throw new Error('verify succeeded but no token returned')
|
|
71
80
|
saveCredentials({
|
|
81
|
+
...loadCredentials(),
|
|
72
82
|
user_id: result.user.id,
|
|
73
83
|
email: result.user.email,
|
|
74
84
|
token: result.token,
|
|
@@ -194,15 +204,235 @@ async function logAction(rest) {
|
|
|
194
204
|
const result = await api('POST', `/v1/runs/${encodeURIComponent(run)}/actions`, {
|
|
195
205
|
channel,
|
|
196
206
|
title,
|
|
197
|
-
url:
|
|
198
|
-
agentmail_thread_id:
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
207
|
+
url: opts.url || undefined,
|
|
208
|
+
agentmail_thread_id: opts['agentmail-thread-id'] || opts['thread-id'] || opts.thread_id || undefined,
|
|
209
|
+
agentmail_inbox_id: opts['agentmail-inbox-id'] || opts.inbox_id || opts.agentmail_inbox_id || undefined,
|
|
210
|
+
recipient: opts.recipient || undefined,
|
|
211
|
+
metadata: opts.metadata ? JSON.parse(readValue(opts.metadata)) : undefined,
|
|
212
|
+
occurred_at: opts['occurred-at'] || opts.occurred_at || undefined,
|
|
202
213
|
})
|
|
203
214
|
console.log(result.id)
|
|
204
215
|
}
|
|
205
216
|
|
|
217
|
+
// ============================================================ mail
|
|
218
|
+
|
|
219
|
+
async function mail(command, rest) {
|
|
220
|
+
if (!command || command === '--help' || command === 'help') return mailUsage()
|
|
221
|
+
if (command === 'setup') return mailSetup(rest)
|
|
222
|
+
if (command === 'send') return mailSend(rest)
|
|
223
|
+
if (command === 'reply') return mailReply(rest, 'reply')
|
|
224
|
+
if (command === 'reply-all') return mailReply(rest, 'reply-all')
|
|
225
|
+
if (command === 'forward') return mailForward(rest)
|
|
226
|
+
if (command === 'threads') return mailThreads(rest)
|
|
227
|
+
if (command === 'thread') return mailThread(rest)
|
|
228
|
+
if (command === 'messages') return mailMessages(rest)
|
|
229
|
+
if (command === 'message') return mailMessage(rest)
|
|
230
|
+
if (command === 'raw') return mailRaw(rest)
|
|
231
|
+
if (command === 'attachment') return mailAttachment(rest)
|
|
232
|
+
if (command === 'request') return mailRequest(rest)
|
|
233
|
+
mailUsage()
|
|
234
|
+
process.exit(1)
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
async function mailSetup(rest) {
|
|
238
|
+
const opts = parseArgs(rest)
|
|
239
|
+
const existing = loadCredentials() || {}
|
|
240
|
+
if (existing.agentmail_token && existing.agentmail_email && !opts.force) {
|
|
241
|
+
console.log(existing.agentmail_email)
|
|
242
|
+
return
|
|
243
|
+
}
|
|
244
|
+
const prefix = required(opts.prefix || defaultInboxPrefix(existing.email), '--prefix')
|
|
245
|
+
const result = await api('POST', '/v1/onboard/agentmail', {
|
|
246
|
+
prefix,
|
|
247
|
+
display_name: opts['display-name'] || opts.display_name || existing.email || prefix,
|
|
248
|
+
purpose: opts.purpose || 'runner',
|
|
249
|
+
})
|
|
250
|
+
saveCredentials({
|
|
251
|
+
...existing,
|
|
252
|
+
agentmail_email: result.agentmail_email || result.email,
|
|
253
|
+
agentmail_inbox_id: result.agentmail_inbox_id || result.inbox_id || result.email,
|
|
254
|
+
agentmail_api_key_id: result.agentmail_api_key_id || result.api_key_id,
|
|
255
|
+
agentmail_token: result.agentmail_token,
|
|
256
|
+
agentmail_saved_at: new Date().toISOString(),
|
|
257
|
+
})
|
|
258
|
+
console.log(result.agentmail_email || result.email)
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
async function mailSend(rest) {
|
|
262
|
+
const opts = parseArgs(rest)
|
|
263
|
+
const creds = requireAgentmailCredentials()
|
|
264
|
+
const to = listOpt(opts.to, '--to')
|
|
265
|
+
const subject = opts.subject || ''
|
|
266
|
+
const body = mailBody(opts, { to, subject })
|
|
267
|
+
const result = await agentmailRequest('POST', `/inboxes/${encodeURIComponent(creds.inboxId)}/messages/send`, body)
|
|
268
|
+
const action = await maybeLogMailAction(opts, {
|
|
269
|
+
kind: 'send',
|
|
270
|
+
defaultTitle: opts.title || `Email: ${subject || to.join(', ')}`,
|
|
271
|
+
recipient: to.join(','),
|
|
272
|
+
subject,
|
|
273
|
+
response: result,
|
|
274
|
+
})
|
|
275
|
+
printJson({ ...result, autark_action_id: action?.id })
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
async function mailReply(rest, mode) {
|
|
279
|
+
const opts = parseArgs(rest)
|
|
280
|
+
const creds = requireAgentmailCredentials()
|
|
281
|
+
const messageId = required(opts['message-id'] || opts.message_id || opts._[0], '--message-id')
|
|
282
|
+
const body = mailBody(opts)
|
|
283
|
+
const endpoint = mode === 'reply-all' ? 'reply-all' : 'reply'
|
|
284
|
+
const result = await agentmailRequest('POST', `/inboxes/${encodeURIComponent(creds.inboxId)}/messages/${encodeURIComponent(messageId)}/${endpoint}`, body)
|
|
285
|
+
const action = await maybeLogMailAction(opts, {
|
|
286
|
+
kind: mode,
|
|
287
|
+
defaultTitle: opts.title || `${mode === 'reply-all' ? 'Reply-all' : 'Reply'}: ${messageId}`,
|
|
288
|
+
recipient: listOpt(opts.to).join(','),
|
|
289
|
+
response: result,
|
|
290
|
+
metadata: { message_id: messageId },
|
|
291
|
+
})
|
|
292
|
+
printJson({ ...result, autark_action_id: action?.id })
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
async function mailForward(rest) {
|
|
296
|
+
const opts = parseArgs(rest)
|
|
297
|
+
const creds = requireAgentmailCredentials()
|
|
298
|
+
const messageId = required(opts['message-id'] || opts.message_id || opts._[0], '--message-id')
|
|
299
|
+
const to = listOpt(opts.to, '--to')
|
|
300
|
+
const body = mailBody(opts, { to, subject: opts.subject || '' })
|
|
301
|
+
const result = await agentmailRequest('POST', `/inboxes/${encodeURIComponent(creds.inboxId)}/messages/${encodeURIComponent(messageId)}/forward`, body)
|
|
302
|
+
const action = await maybeLogMailAction(opts, {
|
|
303
|
+
kind: 'forward',
|
|
304
|
+
defaultTitle: opts.title || `Forward: ${opts.subject || messageId}`,
|
|
305
|
+
recipient: to.join(','),
|
|
306
|
+
response: result,
|
|
307
|
+
metadata: { message_id: messageId },
|
|
308
|
+
})
|
|
309
|
+
printJson({ ...result, autark_action_id: action?.id })
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
async function mailThreads(rest) {
|
|
313
|
+
const opts = parseArgs(rest)
|
|
314
|
+
const creds = requireAgentmailCredentials()
|
|
315
|
+
const qs = queryString({ limit: opts.limit, page_token: opts['page-token'] || opts.page_token })
|
|
316
|
+
printJson(await agentmailRequest('GET', `/inboxes/${encodeURIComponent(creds.inboxId)}/threads${qs}`))
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
async function mailThread(rest) {
|
|
320
|
+
const opts = parseArgs(rest)
|
|
321
|
+
const creds = requireAgentmailCredentials()
|
|
322
|
+
const threadId = required(opts['thread-id'] || opts.thread_id || opts._[0], 'thread_id')
|
|
323
|
+
printJson(await agentmailRequest('GET', `/inboxes/${encodeURIComponent(creds.inboxId)}/threads/${encodeURIComponent(threadId)}`))
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
async function mailMessages(rest) {
|
|
327
|
+
const opts = parseArgs(rest)
|
|
328
|
+
const creds = requireAgentmailCredentials()
|
|
329
|
+
const qs = queryString({ limit: opts.limit, page_token: opts['page-token'] || opts.page_token })
|
|
330
|
+
printJson(await agentmailRequest('GET', `/inboxes/${encodeURIComponent(creds.inboxId)}/messages${qs}`))
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
async function mailMessage(rest) {
|
|
334
|
+
const opts = parseArgs(rest)
|
|
335
|
+
const creds = requireAgentmailCredentials()
|
|
336
|
+
const messageId = required(opts['message-id'] || opts.message_id || opts._[0], 'message_id')
|
|
337
|
+
printJson(await agentmailRequest('GET', `/inboxes/${encodeURIComponent(creds.inboxId)}/messages/${encodeURIComponent(messageId)}`))
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
async function mailRaw(rest) {
|
|
341
|
+
const opts = parseArgs(rest)
|
|
342
|
+
const creds = requireAgentmailCredentials()
|
|
343
|
+
const messageId = required(opts['message-id'] || opts.message_id || opts._[0], 'message_id')
|
|
344
|
+
const raw = await agentmailRequest('GET', `/inboxes/${encodeURIComponent(creds.inboxId)}/messages/${encodeURIComponent(messageId)}/raw`, undefined, { parseJson: false })
|
|
345
|
+
process.stdout.write(raw)
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
async function mailAttachment(rest) {
|
|
349
|
+
const opts = parseArgs(rest)
|
|
350
|
+
const creds = requireAgentmailCredentials()
|
|
351
|
+
const messageId = required(opts['message-id'] || opts.message_id, '--message-id')
|
|
352
|
+
const attachmentId = required(opts['attachment-id'] || opts.attachment_id, '--attachment-id')
|
|
353
|
+
const body = await agentmailRequest('GET', `/inboxes/${encodeURIComponent(creds.inboxId)}/messages/${encodeURIComponent(messageId)}/attachments/${encodeURIComponent(attachmentId)}`, undefined, { parseJson: false })
|
|
354
|
+
if (opts.out) fs.writeFileSync(opts.out, body)
|
|
355
|
+
else process.stdout.write(body)
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
async function mailRequest(rest) {
|
|
359
|
+
const opts = parseArgs(rest)
|
|
360
|
+
const method = (opts.method || opts._[0] || 'GET').toUpperCase()
|
|
361
|
+
const requestPath = required(opts.path || opts._[1], '--path')
|
|
362
|
+
const body = opts.body ? JSON.parse(readValue(opts.body)) : undefined
|
|
363
|
+
const parseJson = opts.raw ? false : true
|
|
364
|
+
const result = await agentmailRequest(method, requestPath.startsWith('/') ? requestPath : `/${requestPath}`, body, { parseJson })
|
|
365
|
+
if (parseJson) printJson(result)
|
|
366
|
+
else process.stdout.write(result)
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
function mailBody(opts, base = {}) {
|
|
370
|
+
const body = { ...base }
|
|
371
|
+
if (opts.text !== undefined) body.text = readValue(opts.text)
|
|
372
|
+
if (opts.html !== undefined) body.html = readValue(opts.html)
|
|
373
|
+
if (opts.subject !== undefined) body.subject = opts.subject
|
|
374
|
+
for (const key of ['cc', 'bcc', 'reply-to', 'label', 'attachment']) {
|
|
375
|
+
const snake = key.replaceAll('-', '_')
|
|
376
|
+
const value = opts[key] ?? opts[snake]
|
|
377
|
+
if (value !== undefined) body[snake] = listOpt(value)
|
|
378
|
+
}
|
|
379
|
+
if (opts.headers !== undefined) body.headers = JSON.parse(readValue(opts.headers))
|
|
380
|
+
return body
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
async function maybeLogMailAction(opts, { kind, defaultTitle, recipient, subject, response, metadata = {} }) {
|
|
384
|
+
const runId = opts['run-id'] || opts.run_id || opts.run
|
|
385
|
+
if (!runId) return null
|
|
386
|
+
const creds = requireAgentmailCredentials()
|
|
387
|
+
const threadId = response?.thread_id
|
|
388
|
+
if (!threadId) throw new Error('AgentMail response missing thread_id; refusing to log email action')
|
|
389
|
+
return api('POST', `/v1/runs/${encodeURIComponent(runId)}/actions`, {
|
|
390
|
+
channel: 'email',
|
|
391
|
+
title: opts.title || defaultTitle,
|
|
392
|
+
recipient: recipient || undefined,
|
|
393
|
+
agentmail_thread_id: threadId,
|
|
394
|
+
agentmail_inbox_id: creds.inboxId,
|
|
395
|
+
metadata: compact({
|
|
396
|
+
kind,
|
|
397
|
+
subject,
|
|
398
|
+
message_id: response?.message_id,
|
|
399
|
+
...metadata,
|
|
400
|
+
}),
|
|
401
|
+
})
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
async function agentmailRequest(method, pathStr, body, { parseJson = true } = {}) {
|
|
405
|
+
const creds = requireAgentmailCredentials()
|
|
406
|
+
const res = await fetch(AGENTMAIL_API + pathStr, {
|
|
407
|
+
method,
|
|
408
|
+
headers: {
|
|
409
|
+
authorization: `Bearer ${creds.token}`,
|
|
410
|
+
'content-type': 'application/json',
|
|
411
|
+
},
|
|
412
|
+
body: body === undefined ? undefined : JSON.stringify(body),
|
|
413
|
+
})
|
|
414
|
+
const text = await res.text()
|
|
415
|
+
let data = text
|
|
416
|
+
if (parseJson) {
|
|
417
|
+
try { data = text ? JSON.parse(text) : null } catch { data = { raw: text } }
|
|
418
|
+
}
|
|
419
|
+
if (!res.ok) {
|
|
420
|
+
const msg = parseJson ? (data?.error || data?.message || JSON.stringify(data)) : text
|
|
421
|
+
throw new Error(`AgentMail ${res.status}: ${msg}`)
|
|
422
|
+
}
|
|
423
|
+
return data
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
function requireAgentmailCredentials() {
|
|
427
|
+
const creds = loadCredentials()
|
|
428
|
+
const token = process.env.AGENTMAIL_API_KEY || creds?.agentmail_token
|
|
429
|
+
const email = process.env.AGENTMAIL_EMAIL || creds?.agentmail_email
|
|
430
|
+
const inboxId = process.env.AGENTMAIL_INBOX_ID || creds?.agentmail_inbox_id || email
|
|
431
|
+
if (!token) throw new Error('missing agentmail_token. run: autark mail setup --prefix <name>')
|
|
432
|
+
if (!inboxId) throw new Error('missing agentmail_inbox_id. run: autark mail setup --prefix <name>')
|
|
433
|
+
return { token, email, inboxId }
|
|
434
|
+
}
|
|
435
|
+
|
|
206
436
|
// ============================================================ context
|
|
207
437
|
|
|
208
438
|
async function context(rest) {
|
|
@@ -223,12 +453,8 @@ async function context(rest) {
|
|
|
223
453
|
const productSlug = parts[0]
|
|
224
454
|
const code = parts[1]
|
|
225
455
|
|
|
226
|
-
if (!code) {
|
|
227
|
-
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
const result = await api('GET', `/v1/context/${encodeURIComponent(productSlug)}/${encodeURIComponent(code)}`)
|
|
231
|
-
return printHypothesisContext(result)
|
|
456
|
+
if (!code) return printProductContext(await api('GET', `/v1/context/${encodeURIComponent(productSlug)}`))
|
|
457
|
+
return printHypothesisContext(await api('GET', `/v1/context/${encodeURIComponent(productSlug)}/${encodeURIComponent(code)}`))
|
|
232
458
|
}
|
|
233
459
|
|
|
234
460
|
function printProductContext(r) {
|
|
@@ -264,9 +490,7 @@ function printHypothesisContext(result) {
|
|
|
264
490
|
console.log(` [${a.channel}] ${a.title}${pointer ? ` → ${pointer}` : ''}`)
|
|
265
491
|
}
|
|
266
492
|
}
|
|
267
|
-
if (run.narrative_md) {
|
|
268
|
-
console.log(`\n${run.narrative_md}`)
|
|
269
|
-
}
|
|
493
|
+
if (run.narrative_md) console.log(`\n${run.narrative_md}`)
|
|
270
494
|
}
|
|
271
495
|
}
|
|
272
496
|
|
|
@@ -301,14 +525,10 @@ function saveCredentials(creds) {
|
|
|
301
525
|
|
|
302
526
|
function loadCredentials() {
|
|
303
527
|
if (!fs.existsSync(CREDS_PATH)) return null
|
|
304
|
-
try {
|
|
305
|
-
return JSON.parse(fs.readFileSync(CREDS_PATH, 'utf8'))
|
|
306
|
-
} catch {
|
|
307
|
-
return null
|
|
308
|
-
}
|
|
528
|
+
try { return JSON.parse(fs.readFileSync(CREDS_PATH, 'utf8')) } catch { return null }
|
|
309
529
|
}
|
|
310
530
|
|
|
311
|
-
// ============================================================
|
|
531
|
+
// ============================================================ utilities
|
|
312
532
|
|
|
313
533
|
function parseArgs(list) {
|
|
314
534
|
const opts = { _: [] }
|
|
@@ -320,12 +540,11 @@ function parseArgs(list) {
|
|
|
320
540
|
}
|
|
321
541
|
const key = arg.slice(2)
|
|
322
542
|
const next = list[i + 1]
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
}
|
|
543
|
+
const value = (!next || next.startsWith('--')) ? true : next
|
|
544
|
+
if (value !== true) i++
|
|
545
|
+
if (opts[key] === undefined) opts[key] = value
|
|
546
|
+
else if (Array.isArray(opts[key])) opts[key].push(value)
|
|
547
|
+
else opts[key] = [opts[key], value]
|
|
329
548
|
}
|
|
330
549
|
return opts
|
|
331
550
|
}
|
|
@@ -338,12 +557,43 @@ function required(value, label) {
|
|
|
338
557
|
}
|
|
339
558
|
|
|
340
559
|
function readValue(value) {
|
|
341
|
-
if (typeof value === 'string' && value.startsWith('@'))
|
|
342
|
-
return fs.readFileSync(value.slice(1), 'utf8')
|
|
343
|
-
}
|
|
560
|
+
if (typeof value === 'string' && value.startsWith('@')) return fs.readFileSync(value.slice(1), 'utf8')
|
|
344
561
|
return String(value)
|
|
345
562
|
}
|
|
346
563
|
|
|
564
|
+
function listOpt(value, label) {
|
|
565
|
+
if (value === undefined || value === null || value === '') {
|
|
566
|
+
if (label) throw new Error(`${label} is required`)
|
|
567
|
+
return []
|
|
568
|
+
}
|
|
569
|
+
const values = Array.isArray(value) ? value : [value]
|
|
570
|
+
return values.flatMap(v => String(v).split(',')).map(v => v.trim()).filter(Boolean)
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
function queryString(params) {
|
|
574
|
+
const q = new URLSearchParams()
|
|
575
|
+
for (const [k, v] of Object.entries(params)) if (v !== undefined && v !== null && v !== '') q.set(k, v)
|
|
576
|
+
const s = q.toString()
|
|
577
|
+
return s ? `?${s}` : ''
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
function defaultInboxPrefix(email) {
|
|
581
|
+
if (!email) return ''
|
|
582
|
+
return String(email).split('@')[0].toLowerCase().replace(/[^a-z0-9._-]/g, '').replace(/^[._-]+/, '').slice(0, 30)
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
function compact(obj) {
|
|
586
|
+
const out = {}
|
|
587
|
+
for (const [k, v] of Object.entries(obj || {})) {
|
|
588
|
+
if (v !== undefined && v !== null && v !== '' && !(Array.isArray(v) && !v.length)) out[k] = v
|
|
589
|
+
}
|
|
590
|
+
return out
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
function printJson(value) {
|
|
594
|
+
console.log(JSON.stringify(value, null, 2))
|
|
595
|
+
}
|
|
596
|
+
|
|
347
597
|
function splitHypothesisRef(ref) {
|
|
348
598
|
const m = String(ref).match(/^([\w.-]+)\/(H\d{2})$/)
|
|
349
599
|
if (!m) throw new Error(`expected product/Hxx, got ${ref}`)
|
|
@@ -353,36 +603,105 @@ function splitHypothesisRef(ref) {
|
|
|
353
603
|
// ============================================================ usage
|
|
354
604
|
|
|
355
605
|
function loginUsage() {
|
|
356
|
-
console.log(`autark login send <email>
|
|
357
|
-
|
|
606
|
+
console.log(`autark login send <email>\nautark login verify <email> --code <6-digit-code>`)
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
function mailUsage() {
|
|
610
|
+
console.log(`autark mail
|
|
611
|
+
|
|
612
|
+
setup --prefix <name> [--force]
|
|
613
|
+
send --to <email[,email]> --subject <s> --text @body.txt [--run-id <id>]
|
|
614
|
+
reply --message-id <id> --text @reply.txt [--run-id <id>]
|
|
615
|
+
reply-all --message-id <id> --text @reply.txt [--run-id <id>]
|
|
616
|
+
forward --message-id <id> --to <email> [--text @body.txt] [--run-id <id>]
|
|
617
|
+
threads [--limit N]
|
|
618
|
+
thread <thread_id>
|
|
619
|
+
messages [--limit N]
|
|
620
|
+
message <message_id>
|
|
621
|
+
raw <message_id>
|
|
622
|
+
attachment --message-id <id> --attachment-id <id> [--out file]
|
|
623
|
+
request <METHOD> <path> [--body @json] [--raw]
|
|
624
|
+
|
|
625
|
+
Mail uses the inbox-scoped token in ~/.autark/credentials.json.`)
|
|
358
626
|
}
|
|
359
627
|
|
|
360
628
|
function usage() {
|
|
361
629
|
console.log(`autark — agent runbook CLI
|
|
362
630
|
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
631
|
+
login send|verify sign in / complete magic code
|
|
632
|
+
logout clear local credentials
|
|
633
|
+
me show signed-in user
|
|
634
|
+
|
|
635
|
+
product upsert|list create/edit/list products
|
|
636
|
+
hypothesis create|status create or update hypotheses
|
|
637
|
+
run start|finish start / finish a run
|
|
638
|
+
log action record one outreach touch
|
|
639
|
+
context [<slug>|...] pull product or hypothesis context
|
|
640
|
+
|
|
641
|
+
mail setup|send|reply|... send/read mail via your AgentMail inbox
|
|
367
642
|
|
|
368
|
-
|
|
369
|
-
|
|
643
|
+
For details on any group: autark <group> --help
|
|
644
|
+
Examples: autark product --help
|
|
645
|
+
autark mail --help`)
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
function productUsage() {
|
|
649
|
+
console.log(`autark product
|
|
650
|
+
|
|
651
|
+
upsert --slug <slug> --name <name> [--url <url>] [--tagline <text>]
|
|
652
|
+
[--brief @./brief.md] [--visibility private|public]
|
|
653
|
+
|
|
654
|
+
list prints slug, visibility, id, name
|
|
655
|
+
|
|
656
|
+
The brief is the markdown the agent reads at the top of every run
|
|
657
|
+
to understand what the product is and who it might serve.`)
|
|
658
|
+
}
|
|
370
659
|
|
|
371
|
-
|
|
372
|
-
autark hypothesis
|
|
373
|
-
|
|
374
|
-
|
|
660
|
+
function hypothesisUsage() {
|
|
661
|
+
console.log(`autark hypothesis
|
|
662
|
+
|
|
663
|
+
create --product-id <id> --md @./hyp.md [--code H01] [--title <t>] [--status active|inactive|dead]
|
|
664
|
+
create --product <slug> --md @./hyp.md [--code H01] [--title <t>] # slug alias
|
|
665
|
+
|
|
666
|
+
status --hypothesis-id <id> --status active|inactive|dead
|
|
667
|
+
status <slug>/<H01> --status active|inactive|dead # slug/code alias
|
|
668
|
+
|
|
669
|
+
Hypotheses are frozen on create. Only --status changes after.
|
|
670
|
+
If --code is omitted, autark picks the next H## for the product.`)
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
function runUsage() {
|
|
674
|
+
console.log(`autark run
|
|
675
|
+
|
|
676
|
+
start --hypothesis-id <id>
|
|
677
|
+
start --hypothesis <slug>/<H01> # slug/code alias
|
|
678
|
+
→ prints RUN_ID to stdout
|
|
679
|
+
|
|
680
|
+
finish --run-id <id> --narrative @./run.md
|
|
681
|
+
→ seals the run with the narrative blob`)
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
function logUsage() {
|
|
685
|
+
console.log(`autark log
|
|
686
|
+
|
|
687
|
+
action --run-id <id> --channel <c> --title <t>
|
|
688
|
+
[--url <u>] # github / reddit / hn / blog / gist / linkedin
|
|
689
|
+
[--agentmail-thread-id <uuid>] # email
|
|
690
|
+
[--agentmail-inbox-id <email>] # email — defaults to your inbox
|
|
691
|
+
[--recipient <email>] # email
|
|
692
|
+
[--metadata @./meta.json] # any channel-specific extras
|
|
693
|
+
|
|
694
|
+
One row per external touch. Body content stays in AgentMail/GitHub/etc;
|
|
695
|
+
this just records the pointer + the title agents see on the dashboard.`)
|
|
696
|
+
}
|
|
375
697
|
|
|
376
|
-
|
|
377
|
-
autark
|
|
378
|
-
autark run finish --run-id <id> --narrative @./run.md
|
|
698
|
+
function contextUsage() {
|
|
699
|
+
console.log(`autark context
|
|
379
700
|
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
701
|
+
<slug> product brief + every hypothesis (active/inactive/dead)
|
|
702
|
+
<slug>/<H01> one hypothesis: text + every run + every action
|
|
703
|
+
--product-id <id> product brief + every hypothesis
|
|
704
|
+
--hypothesis-id <id> one hypothesis: text + every run + every action
|
|
383
705
|
|
|
384
|
-
|
|
385
|
-
autark context --hypothesis-id <id>
|
|
386
|
-
autark context <slug>
|
|
387
|
-
autark context <slug>/<H01>`)
|
|
706
|
+
Use product-level first to pick which H## to drill into.`)
|
|
388
707
|
}
|
package/package.json
CHANGED