autark-cli 0.1.3 → 0.1.4

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 (3) hide show
  1. package/README.md +20 -12
  2. package/autark.mjs +93 -51
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -20,26 +20,34 @@ autark login verify your@email.com --code 123456
20
20
 
21
21
  ## Use
22
22
 
23
+ Autark is ID-first. Use `product list` / `context` to discover IDs, then pass IDs for writes. `slug/H01` remains a convenience alias.
24
+
23
25
  ```sh
24
26
  # products
25
- autark product upsert --slug chrome-relay --name "chrome-relay" --tagline "..."
26
- autark product list
27
+ autark product upsert --slug chrome-relay --name "Chrome Relay" --tagline "..."
28
+ autark product list # prints slug, visibility, id, name
29
+ autark context chrome-relay
30
+
31
+ autark context --product-id <product_id>
27
32
 
28
33
  # hypotheses
29
- autark hypothesis create --product chrome-relay --code H01 --md @./H01.md --title "..."
30
- autark hypothesis status chrome-relay/H01 --status active # active|inactive|dead
34
+ 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
31
42
 
32
43
  # runs
33
- autark run start --hypothesis chrome-relay/H01
34
- autark run finish --run <run_id> --narrative @./narrative.md
44
+ autark run start --hypothesis-id <hypothesis_id>
45
+ autark run finish --run-id <run_id> --narrative @./narrative.md
35
46
 
36
47
  # actions (anything the agent did during a run)
37
- autark log action --run <run_id> --channel email --title "..." \
48
+ autark log action --run-id <run_id> --channel email --title "..." \
38
49
  --recipient person@example.com --agentmail-thread-id <thread_id>
39
- autark log action --run <run_id> --channel github --title "..." --url https://github.com/...
40
-
41
- # context (full hypothesis state for an agent picking up work)
42
- autark context chrome-relay/H01
50
+ autark log action --run-id <run_id> --channel github --title "..." --url https://github.com/...
43
51
 
44
52
  autark me
45
53
  autark logout
@@ -53,4 +61,4 @@ autark logout
53
61
 
54
62
  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.
55
63
 
56
- The web at autark.kushalsm.com is read-only. All writes (product, hypothesis, run, action) come through this CLI or future MCP server.
64
+ The web at autark.kushalsm.com reads from InstantDB. Writes should go through the Worker/CLI path; dashboard direct writes are being phased out.
package/autark.mjs CHANGED
@@ -4,10 +4,8 @@
4
4
  // Auth: magic-link flow saves a refresh_token to ~/.autark/credentials.json.
5
5
  // Every subsequent command sends it as `Authorization: Bearer <token>`.
6
6
  //
7
- // All business logic, schema knowledge, and InstantDB connectivity lives in
8
- // the Worker. The CLI is intentionally thin — it parses args, makes HTTP
9
- // calls, formats output. Replace this binary in any language tomorrow and
10
- // the system still works.
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.
11
9
 
12
10
  import fs from 'node:fs'
13
11
  import os from 'node:os'
@@ -116,7 +114,7 @@ async function productList() {
116
114
  return
117
115
  }
118
116
  for (const p of result.products) {
119
- console.log(`${(p.slug || '').padEnd(20)} ${(p.visibility || '').padEnd(8)} ${p.name || ''}`)
117
+ console.log(`${(p.slug || '').padEnd(20)} ${(p.visibility || '').padEnd(8)} ${(p.id || '').padEnd(36)} ${p.name || ''}`)
120
118
  }
121
119
  }
122
120
 
@@ -124,45 +122,65 @@ async function productList() {
124
122
 
125
123
  async function hypothesisCreate(rest) {
126
124
  const opts = parseArgs(rest)
127
- const product = required(opts.product, '--product')
128
- const code = required(opts.code, '--code')
129
- const md = readValue(required(opts.md, '--md'))
130
- const title = opts.title
131
- const result = await api('POST', '/v1/hypotheses', {
132
- product, code, md,
133
- title: title || undefined,
125
+ const md = readValue(required(opts.md, '--md'))
126
+ const body = {
127
+ md,
128
+ code: opts.code || undefined,
129
+ title: opts.title || undefined,
134
130
  status: opts.status || undefined,
135
- })
131
+ }
132
+
133
+ let result
134
+ if (opts['product-id'] || opts.product_id) {
135
+ const productId = opts['product-id'] || opts.product_id
136
+ result = await api('POST', `/v1/products/${encodeURIComponent(productId)}/hypotheses`, body)
137
+ } else {
138
+ body.product = required(opts.product, '--product or --product-id')
139
+ result = await api('POST', '/v1/hypotheses', body)
140
+ }
136
141
  console.log(result.id)
137
142
  }
138
143
 
139
144
  async function hypothesisStatus(rest) {
140
145
  const opts = parseArgs(rest)
141
- const ref = required(opts._[0] || opts.hypothesis, 'hypothesis (e.g. chrome-relay/H01)')
142
146
  const status = required(opts.status, '--status')
143
- const [productSlug, code] = splitHypothesisRef(ref)
144
- const ctx = await api('GET', `/v1/context/${encodeURIComponent(productSlug)}/${encodeURIComponent(code)}`)
145
- const hypId = ctx.hypothesis?.id
146
- if (!hypId) throw new Error(`hypothesis not found: ${ref}`)
147
- const result = await api('PATCH', `/v1/hypotheses/${hypId}/status`, { status })
148
- console.log(`${ref} → ${result.status}`)
147
+ let hypId = opts['hypothesis-id'] || opts.hypothesis_id || opts.id
148
+ let label = hypId
149
+
150
+ if (!hypId) {
151
+ const ref = required(opts._[0] || opts.hypothesis, 'hypothesis id or product/Hxx')
152
+ const [productSlug, code] = splitHypothesisRef(ref)
153
+ const ctx = await api('GET', `/v1/context/${encodeURIComponent(productSlug)}/${encodeURIComponent(code)}`)
154
+ hypId = ctx.hypothesis?.id
155
+ label = ref
156
+ }
157
+ if (!hypId) throw new Error('hypothesis not found')
158
+
159
+ const result = await api('PATCH', `/v1/hypotheses/${encodeURIComponent(hypId)}/status`, { status })
160
+ console.log(`${label} → ${result.status}`)
149
161
  }
150
162
 
151
163
  // =============================================================== runs
152
164
 
153
165
  async function runStart(rest) {
154
166
  const opts = parseArgs(rest)
155
- const ref = required(opts.hypothesis || opts._[0], '--hypothesis (e.g. chrome-relay/H01)')
156
- const [productSlug, code] = splitHypothesisRef(ref)
157
- const result = await api('POST', '/v1/runs', { product: productSlug, hypothesis: code })
167
+ let result
168
+ const hypId = opts['hypothesis-id'] || opts.hypothesis_id
169
+ if (hypId) {
170
+ result = await api('POST', `/v1/hypotheses/${encodeURIComponent(hypId)}/runs`, {})
171
+ } else {
172
+ const ref = required(opts.hypothesis || opts._[0], '--hypothesis-id <id> or --hypothesis <slug/Hxx>')
173
+ const [productSlug, code] = splitHypothesisRef(ref)
174
+ result = await api('POST', '/v1/runs', { product: productSlug, hypothesis: code })
175
+ }
158
176
  console.log(result.id)
159
177
  }
160
178
 
161
179
  async function runFinish(rest) {
162
180
  const opts = parseArgs(rest)
163
- const id = required(opts.run, '--run')
181
+ const id = required(opts['run-id'] || opts.run_id || opts.run, '--run-id')
164
182
  const narrative = readValue(required(opts.narrative, '--narrative'))
165
- const result = await api('PATCH', `/v1/runs/${id}/finish`, { narrative_md: narrative })
183
+ const result = await api('PATCH', `/v1/runs/${encodeURIComponent(id)}/finish`, { narrative_md: narrative })
166
184
  console.log(`${result.id} finished_at=${result.finished_at}`)
167
185
  }
168
186
 
@@ -170,11 +188,10 @@ async function runFinish(rest) {
170
188
 
171
189
  async function logAction(rest) {
172
190
  const opts = parseArgs(rest)
173
- const run = required(opts.run, '--run')
191
+ const run = required(opts['run-id'] || opts.run_id || opts.run, '--run-id')
174
192
  const channel = required(opts.channel, '--channel')
175
193
  const title = required(opts.title, '--title')
176
- const result = await api('POST', '/v1/actions', {
177
- run,
194
+ const result = await api('POST', `/v1/runs/${encodeURIComponent(run)}/actions`, {
178
195
  channel,
179
196
  title,
180
197
  url: opts.url || undefined,
@@ -190,32 +207,51 @@ async function logAction(rest) {
190
207
 
191
208
  async function context(rest) {
192
209
  const opts = parseArgs(rest)
193
- const ref = required(opts._[0] || opts.hypothesis, 'product or hypothesis (e.g. chrome-relay or chrome-relay/H01)')
210
+
211
+ if (opts['product-id'] || opts.product_id) {
212
+ const productId = opts['product-id'] || opts.product_id
213
+ return printProductContext(await api('GET', `/v1/products/${encodeURIComponent(productId)}/context`))
214
+ }
215
+
216
+ if (opts['hypothesis-id'] || opts.hypothesis_id) {
217
+ const hypId = opts['hypothesis-id'] || opts.hypothesis_id
218
+ return printHypothesisContext(await api('GET', `/v1/hypotheses/${encodeURIComponent(hypId)}/context`))
219
+ }
220
+
221
+ const ref = required(opts._[0] || opts.hypothesis, 'product slug, product/Hxx, --product-id, or --hypothesis-id')
194
222
  const parts = ref.split('/')
195
223
  const productSlug = parts[0]
196
224
  const code = parts[1]
197
225
 
198
226
  if (!code) {
199
- // product-level: brief + every hypothesis with status + counts
200
- const r = await api('GET', `/v1/context/${encodeURIComponent(productSlug)}`)
201
- console.log(`# ${r.product.slug} — ${r.product.name}`)
202
- if (r.product.tagline) console.log(`> ${r.product.tagline}`)
203
- if (r.product.url) console.log(`> ${r.product.url}`)
204
- console.log(`\n## Brief\n`)
205
- console.log(r.product.brief?.trim() || '(no brief set — owner can add one at https://autark.kushalsm.com)')
206
- console.log(`\n## Hypotheses (${r.hypotheses.length})\n`)
207
- if (!r.hypotheses.length) {
208
- console.log('(none yet — start with H01)')
209
- } else {
210
- for (const h of r.hypotheses) {
211
- console.log(`- [${h.status}] ${h.code} — ${h.title} (runs: ${h.run_count})`)
212
- }
213
- }
214
- return
227
+ return printProductContext(await api('GET', `/v1/context/${encodeURIComponent(productSlug)}`))
215
228
  }
216
229
 
217
230
  const result = await api('GET', `/v1/context/${encodeURIComponent(productSlug)}/${encodeURIComponent(code)}`)
231
+ return printHypothesisContext(result)
232
+ }
233
+
234
+ function printProductContext(r) {
235
+ console.log(`# ${r.product.slug} — ${r.product.name}`)
236
+ console.log(`id: ${r.product.id}`)
237
+ if (r.product.tagline) console.log(`> ${r.product.tagline}`)
238
+ if (r.product.url) console.log(`> ${r.product.url}`)
239
+ console.log(`\n## Brief\n`)
240
+ console.log(r.product.brief?.trim() || '(no brief set — owner can add one at https://autark.kushalsm.com)')
241
+ console.log(`\n## Hypotheses (${r.hypotheses.length})\n`)
242
+ if (!r.hypotheses.length) {
243
+ console.log('(none yet — create one with: autark hypothesis create --product-id <product_id> --md @hyp.md)')
244
+ } else {
245
+ for (const h of r.hypotheses) {
246
+ console.log(`- [${h.status}] ${h.code} id=${h.id} — ${h.title} (runs: ${h.run_count})`)
247
+ }
248
+ }
249
+ }
250
+
251
+ function printHypothesisContext(result) {
218
252
  console.log(`# ${result.product.slug}/${result.hypothesis.code} — ${result.hypothesis.title}\n`)
253
+ console.log(`product_id: ${result.product.id}`)
254
+ console.log(`hypothesis_id: ${result.hypothesis.id}`)
219
255
  console.log(`Status: ${result.hypothesis.status}\n`)
220
256
  console.log(result.hypothesis.hypothesis_md)
221
257
  for (const run of result.runs) {
@@ -330,17 +366,23 @@ function usage() {
330
366
  autark me show signed-in user
331
367
 
332
368
  autark product upsert --slug <slug> --name <name> [--url <url>] [--tagline <text>] [--brief @./brief.md] [--visibility private|public]
333
- autark product list
369
+ autark product list prints slug, visibility, id, name
334
370
 
335
- autark hypothesis create --product <slug> --code H01 --md @./hypothesis.md [--title <t>]
336
- autark hypothesis status <slug>/<H01> --status active|inactive|dead
371
+ autark hypothesis create --product-id <id> --md @./hyp.md [--code H01] [--title <t>]
372
+ autark hypothesis create --product <slug> --md @./hyp.md [--code H01] [--title <t>] # alias
373
+ autark hypothesis status --hypothesis-id <id> --status active|inactive|dead
374
+ autark hypothesis status <slug>/<H01> --status active|inactive|dead # alias
337
375
 
338
- autark run start --hypothesis <slug>/<H01>
339
- autark run finish --run <id> --narrative @./run.md
376
+ autark run start --hypothesis-id <id>
377
+ autark run start --hypothesis <slug>/<H01> # alias
378
+ autark run finish --run-id <id> --narrative @./run.md
340
379
 
341
- autark log action --run <id> --channel <c> --title <t> [--url <u>]
380
+ autark log action --run-id <id> --channel <c> --title <t> [--url <u>]
342
381
  [--agentmail-thread-id <uuid>] [--recipient <email>]
343
382
  [--metadata @./meta.json]
344
383
 
384
+ autark context --product-id <id>
385
+ autark context --hypothesis-id <id>
386
+ autark context <slug>
345
387
  autark context <slug>/<H01>`)
346
388
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "autark-cli",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
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",