@figs-so/cli 0.1.2 → 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 CHANGED
@@ -9,14 +9,15 @@ manager's window into the agents a company runs as back-office employees.
9
9
  ## Use
10
10
 
11
11
  ```bash
12
- npx @figs-so/cli@latest login # browser approve (device flow)
13
- npx @figs-so/cli@latest workspaces # list your workspaces
14
- npx @figs-so/cli@latest init --workspace <id> # writes .figs/config.json + GUIDE.md
15
- npx @figs-so/cli@latest doctor # validate .figs/ before pushing
16
- npx @figs-so/cli@latest push # publish .figs/ to Figs
12
+ npx @figs-so/cli@latest login # browser approve (device flow)
13
+ npx @figs-so/cli@latest workspaces # list your workspaces (shows the slug)
14
+ npx @figs-so/cli@latest init --workspace <slug> # writes .figs/config.json + GUIDE.md
15
+ npx @figs-so/cli@latest doctor # validate .figs/ before pushing
16
+ npx @figs-so/cli@latest push # publish .figs/ to Figs
17
17
  ```
18
18
 
19
- After `init`, read **`.figs/GUIDE.md`** for the full agent integration guide.
19
+ The full, always-current guide + `agent.json` schema is served at **`<endpoint>/llms.txt`**
20
+ (`figs init` drops a thin pointer to it at `.figs/GUIDE.md`).
20
21
 
21
22
  ## Commands
22
23
 
@@ -24,8 +25,8 @@ After `init`, read **`.figs/GUIDE.md`** for the full agent integration guide.
24
25
  |---|---|
25
26
  | `figs status [--json]` | login / workspace / agent state |
26
27
  | `figs login` | device-flow browser approve (or `figs login <token>`) |
27
- | `figs workspaces [--create <name>] [--json]` | list / create workspaces |
28
- | `figs init --workspace <id>` | generate identity UUID + config + GUIDE.md |
28
+ | `figs workspaces [--json]` | list your workspaces (read-only; create one in the web app) |
29
+ | `figs init --workspace <slug-or-id>` | resolve the workspace, generate identity UUID + config + pointer GUIDE.md |
29
30
  | `figs doctor` | validate `.figs/` against the contract |
30
31
  | `figs push` | one-way publish of `.figs/` |
31
32
  | `figs version` | print version + check for updates |
package/figs.mjs CHANGED
@@ -5,8 +5,8 @@
5
5
  * figs status show login / workspace / agent state [--json]
6
6
  * figs login browser approve (device flow) — agent never sees the token
7
7
  * figs login <token> fallback: save a token you pasted (~/.figs/credentials.json)
8
- * figs workspaces [--create <name>] list (or create) the user's workspaces [--json]
9
- * figs init --workspace <id> [--endpoint <url>]
8
+ * figs workspaces list the user's workspaces [--json]
9
+ * figs init --workspace <slug-or-id> [--endpoint <url>]
10
10
  * create .figs/config.json + GUIDE.md (generates a stable agent id)
11
11
  * figs doctor validate .figs/ against the contract before pushing
12
12
  * figs push one-way push the .figs/ spine to the ingest endpoint
@@ -28,13 +28,10 @@ import {
28
28
  writeFileSync,
29
29
  } from "node:fs"
30
30
  import { homedir } from "node:os"
31
- import { dirname, join } from "node:path"
32
- import { fileURLToPath } from "node:url"
31
+ import { join } from "node:path"
33
32
  import { randomUUID } from "node:crypto"
34
33
 
35
- const cliDir = dirname(fileURLToPath(import.meta.url))
36
-
37
- const VERSION = "0.1.2"
34
+ const VERSION = "0.1.5"
38
35
  // Going-forward default; override with FIGS_ENDPOINT or .figs/config.json endpoint
39
36
  // (e.g. FIGS_ENDPOINT=http://localhost:3000 for local dev).
40
37
  const DEFAULT_ENDPOINT = "https://app.figs.so"
@@ -148,7 +145,7 @@ async function checkVersion({ force = false, hardFail = false } = {}) {
148
145
  if (cmd === "login") await login(process.argv[3])
149
146
  else if (cmd === "status") await status()
150
147
  else if (cmd === "workspaces") await workspaces()
151
- else if (cmd === "init") init()
148
+ else if (cmd === "init") await init()
152
149
  else if (cmd === "doctor") await doctor()
153
150
  else if (cmd === "push") await push()
154
151
  else if (cmd === "version" || cmd === "--version") {
@@ -200,6 +197,10 @@ async function login(token) {
200
197
  }
201
198
  if (status === "denied") die("authorization denied")
202
199
  if (status === "expired") die("code expired — run `figs login` again")
200
+ if (status === "already_claimed") {
201
+ die("this login was already completed (on another run) — run `figs status` to check; re-run `figs login` if no token was saved")
202
+ }
203
+ if (status === "not_found") die("login code not found — run `figs login` again")
203
204
  // pending → keep polling
204
205
  }
205
206
  die("timed out waiting for approval — run `figs login` again")
@@ -210,6 +211,7 @@ async function status() {
210
211
  const token = getToken()
211
212
  const cfg = readJson(join(repoDir, "config.json"), null)
212
213
  const hasAgent = existsSync(join(repoDir, "agent.json"))
214
+ const hasContract = existsSync(join(repoDir, "CONTRACT.md"))
213
215
  const endpoint = resolveEndpoint()
214
216
 
215
217
  let loggedIn = false
@@ -232,6 +234,7 @@ async function status() {
232
234
  workspaces: list?.map((w) => ({ id: w.id, name: w.name, role: w.role })),
233
235
  config: cfg ? { workspaceId: cfg.workspaceId, agentId: cfg.agentId } : null,
234
236
  agentJson: hasAgent,
237
+ contractMd: hasContract,
235
238
  },
236
239
  null,
237
240
  2,
@@ -258,45 +261,98 @@ async function status() {
258
261
  ? cfg.workspaceId
259
262
  : "not initialized — run `figs init --workspace <id>`",
260
263
  )
261
- row("agent.json", hasAgent ? "present" : "missing — author .figs/agent.json")
264
+ row("agent.json", hasAgent ? "present (identity)" : "missing — author .figs/agent.json")
265
+ row(
266
+ "contract",
267
+ hasContract
268
+ ? "present (activity) — follow it"
269
+ : "none yet — Activity is optional, agree it with your user",
270
+ )
262
271
  row("endpoint", endpoint)
263
272
  row("cli", VERSION)
264
273
  }
265
274
 
266
- /** List the user's workspaces, or create one with --create "<name>". */
275
+ /**
276
+ * List the user's workspaces (read-only). Creating a workspace is a human,
277
+ * web-side action — the agent only ever joins one it was pointed at. Surfaces
278
+ * the slug, which is what `figs init --workspace <slug>` takes.
279
+ */
267
280
  async function workspaces() {
268
- const createName = flag("--create")
269
- if (createName) {
270
- const { workspace: ws } = await api("POST", "/api/workspaces", {
271
- name: createName,
272
- })
273
- if (JSON_OUT) return void console.log(JSON.stringify(ws, null, 2))
274
- console.log(
275
- `figs: ✓ created "${ws.name}" (${ws.id}) — you're the owner`,
276
- )
277
- return
278
- }
279
-
280
281
  const { workspaces: list = [] } = await api("GET", "/api/workspaces")
281
282
  if (JSON_OUT) return void console.log(JSON.stringify(list, null, 2))
282
283
  if (list.length === 0) {
283
- console.log('figs: no workspaces yet — `figs workspaces --create "<name>"`')
284
+ console.log("figs: no workspaces yet — create one in the Figs web app, then re-run.")
284
285
  return
285
286
  }
286
287
  console.log(`figs: ${list.length} workspace${list.length === 1 ? "" : "s"}`)
287
288
  for (const w of list) {
288
- console.log(` ${String(w.role).padEnd(6)} ${w.name} ${w.id}`)
289
+ console.log(` ${String(w.role).padEnd(6)} ${w.slug} ${w.name}`)
289
290
  }
291
+ console.log(" → figs init --workspace <slug>")
292
+ }
293
+
294
+ function isUuid(s) {
295
+ return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(
296
+ String(s),
297
+ )
290
298
  }
291
299
 
292
- function init() {
293
- const workspaceId = flag("--workspace")
294
- if (!workspaceId) {
295
- die("usage: figs init --workspace <workspaceId> [--endpoint <url>]")
300
+ /**
301
+ * The local `.figs/GUIDE.md` is a thin orientation breadcrumb (safe to
302
+ * regenerate) — the full, always-current guide + schema is served at
303
+ * `${endpoint}/llms.txt`, and the agent's own standing agreement lives in the
304
+ * precious, agent-authored `.figs/CONTRACT.md` (which `init` never touches).
305
+ */
306
+ function guideStub(endpoint) {
307
+ return `<!-- Written by \`figs init\` (safe to regenerate). Orientation only — the full
308
+ guide lives at the URL below; your own contract lives in CONTRACT.md. -->
309
+ # Figs — this folder
310
+
311
+ This repo publishes to **Figs** (your manager's read-only window into what you do).
312
+
313
+ - **Full guide + schema → ${endpoint}/llms.txt** — read this first.
314
+ - **Your contract → ./CONTRACT.md** — what you publish / hold back (you + your user author it).
315
+ - **Identity → config.json** (committed) · **charter → agent.json** (you write).
316
+
317
+ **Where are you?** You may be reading this at any time — don't assume you're starting fresh. Run
318
+ \`figs status\` and check \`.figs/\` before acting:
319
+ • not set up yet → author \`.figs/agent.json\`, then \`figs doctor\` && \`figs push\` to appear (Identity).
320
+ • CONTRACT.md present → follow it; keep publishing what it says.
321
+ • no CONTRACT.md yet → agree with your user what work to surface (Activity) — see ${endpoint}/llms.txt.
322
+
323
+ Commit config.json + agent.json + CONTRACT.md + this file. runs.jsonl / asks.jsonl / artifacts/
324
+ are a gitignored outbox. The token is the human's job — never generate one yourself.
325
+ `
326
+ }
327
+
328
+ async function init() {
329
+ const workspaceArg = flag("--workspace")
330
+ if (!workspaceArg) {
331
+ die("usage: figs init --workspace <slug-or-id> [--endpoint <url>]")
332
+ }
333
+ const endpoint = (flag("--endpoint") || resolveEndpoint()).replace(/\/+$/, "")
334
+
335
+ // A UUID is the canonical workspace id (used as-is, no network). A slug is the
336
+ // human-friendly handle — resolve it to its UUID via the API, and store the
337
+ // UUID so a later workspace rename can't break this agent's pushes.
338
+ let workspaceId = workspaceArg
339
+ if (!isUuid(workspaceArg)) {
340
+ if (!getToken()) {
341
+ die("not logged in — run `figs login` first (resolving a workspace slug needs auth; or pass the workspace UUID)")
342
+ }
343
+ const r = await request("GET", "/api/workspaces", null, getToken())
344
+ if (!r.ok) {
345
+ die(`could not resolve workspace "${workspaceArg}" (${r.status}): ${r.data.error ?? r.data.raw ?? ""}`)
346
+ }
347
+ const list = r.data.workspaces ?? []
348
+ const match = list.find((w) => w.slug === workspaceArg || w.id === workspaceArg)
349
+ if (!match) {
350
+ die(`no workspace matching "${workspaceArg}" — run \`figs workspaces\` to see yours`)
351
+ }
352
+ workspaceId = match.id
296
353
  }
354
+
297
355
  const existing = readJson(join(repoDir, "config.json"), null)
298
- const endpoint =
299
- flag("--endpoint") || existing?.endpoint || DEFAULT_ENDPOINT
300
356
  const agentId = existing?.agentId || randomUUID()
301
357
  mkdirSync(repoDir, { recursive: true })
302
358
  writeFileSync(
@@ -310,7 +366,7 @@ function init() {
310
366
  writeFileSync(
311
367
  giPath,
312
368
  [
313
- "# Figs — commit config.json + agent.json (identity + charter).",
369
+ "# Figs — commit config.json + agent.json + CONTRACT.md + GUIDE.md.",
314
370
  "# Activity is a transient outbox: emitted per run, aggregated remotely.",
315
371
  "runs.jsonl",
316
372
  "asks.jsonl",
@@ -320,22 +376,15 @@ function init() {
320
376
  ].join("\n"),
321
377
  )
322
378
  }
323
- // Emit the agent guide (refreshed on every init).
324
- let guide = ""
325
- try {
326
- writeFileSync(
327
- join(repoDir, "GUIDE.md"),
328
- readFileSync(join(cliDir, "GUIDE.template.md"), "utf8"),
329
- )
330
- guide = " + GUIDE.md"
331
- } catch {
332
- // template not shipped (dev) — non-fatal
333
- }
379
+ writeFileSync(join(repoDir, "GUIDE.md"), guideStub(endpoint))
334
380
 
335
381
  console.log(
336
- `figs: ✓ .figs/config.json + .gitignore${guide} written (agentId ${agentId})`,
382
+ `figs: ✓ .figs/config.json + .gitignore + GUIDE.md written (agentId ${agentId})`,
383
+ )
384
+ console.log(
385
+ ` Phase 1: author .figs/agent.json (your charter), then \`figs doctor\` && \`figs push\` to appear.`,
337
386
  )
338
- console.log(" read .figs/GUIDE.md, then author .figs/agent.json and run `figs doctor`.")
387
+ console.log(` Full guide: ${endpoint}/llms.txt`)
339
388
  }
340
389
 
341
390
  /** Validate the local .figs/ payload against the contract — no write. */
@@ -408,8 +457,10 @@ async function push() {
408
457
  * The spine ingest is JSON-only; artifacts go to a separate endpoint that stores
409
458
  * them content-addressed (an unchanged file is skipped server-side). Content is
410
459
  * sent base64-encoded so any type — html, markdown, text, json, images — survives.
411
- * Files larger than ~3 MB are rejected by the server; compress images if needed.
412
- * Missing files are warned, not fatal.
460
+ * A **server rejection** (auth/size/etc.) is fatal: it prints and exits non-zero
461
+ * so the agent never believes a report published when it didn't (esp. the ~3 MB
462
+ * cap → 413). A **missing local file** is only a warning — that's the agent
463
+ * referencing an artifact it didn't actually produce, not a publish failure.
413
464
  */
414
465
  async function pushArtifacts(base, token, config, runs, asks) {
415
466
  const refNames = (asks ?? []).flatMap((a) =>
@@ -422,10 +473,13 @@ async function pushArtifacts(base, token, config, runs, asks) {
422
473
 
423
474
  let uploaded = 0
424
475
  let unchanged = 0
476
+ let missing = 0
477
+ let failed = 0
425
478
  for (const name of names) {
426
479
  const p = join(repoDir, "artifacts", name)
427
480
  if (!existsSync(p)) {
428
481
  console.warn(`figs: ! artifact missing, skipped: artifacts/${name}`)
482
+ missing++
429
483
  continue
430
484
  }
431
485
  const content = readFileSync(p).toString("base64")
@@ -440,8 +494,13 @@ async function pushArtifacts(base, token, config, runs, asks) {
440
494
  }),
441
495
  })
442
496
  if (!res.ok) {
443
- const t = await res.text()
444
- console.warn(`figs: ! artifact upload failed (${res.status}) ${name}: ${t}`)
497
+ const t = await res.text().catch(() => "")
498
+ const hint =
499
+ res.status === 413 ? " — too large (>3 MB); compress or split it" : ""
500
+ console.error(
501
+ `figs: ✗ artifact upload failed (${res.status}) ${name}${hint}${t ? `: ${t}` : ""}`,
502
+ )
503
+ failed++
445
504
  continue
446
505
  }
447
506
  const body = await res.json().catch(() => ({}))
@@ -449,8 +508,13 @@ async function pushArtifacts(base, token, config, runs, asks) {
449
508
  else uploaded++
450
509
  }
451
510
  console.log(
452
- `figs: ✓ artifacts — ${uploaded} uploaded, ${unchanged} unchanged`,
511
+ `figs: ${failed ? "✗" : ""} artifacts — ${uploaded} uploaded, ${unchanged} unchanged` +
512
+ (missing ? `, ${missing} missing` : "") +
513
+ (failed ? `, ${failed} failed` : ""),
453
514
  )
515
+ // The spine already landed; signal a non-zero exit so an agent's run loop can
516
+ // catch that an artifact the manager needs to read did not publish.
517
+ if (failed) process.exit(1)
454
518
  }
455
519
 
456
520
  function readJsonl(name) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@figs-so/cli",
3
- "version": "0.1.2",
3
+ "version": "0.1.5",
4
4
  "description": "Figs CLI — publish your AI agent's state to Figs (figs.so). Run by the agent.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -8,7 +8,6 @@
8
8
  },
9
9
  "files": [
10
10
  "figs.mjs",
11
- "GUIDE.template.md",
12
11
  "README.md"
13
12
  ],
14
13
  "engines": {
package/GUIDE.template.md DELETED
@@ -1,147 +0,0 @@
1
- <!-- This file is emitted by `figs init`. Re-run `figs init` to refresh it. -->
2
- # Figs — how this `.figs/` folder works
3
-
4
- You are an AI employee. **Figs** is the window your manager uses to see what you're
5
- responsible for, what you've done, and what you need from them. You publish your state to it;
6
- it's a **read-only mirror** — it never reaches back into your repo.
7
-
8
- Schema details here mirror Figs' contract (the source of truth). After you edit anything, run
9
- **`figs doctor`** — it validates `.figs/` against the live contract and tells you what's wrong.
10
-
11
- ## The model: `.figs/` is your `dist/`
12
-
13
- Everything you want visible goes in the `.figs/` folder, and `figs push` publishes it.
14
- *If it's in `.figs/`, it's shared; if not, it's private.* The sync is **one-way,
15
- append-mostly, and never deletes** on the server — the remote is the durable record, so a run
16
- your manager signed off on doesn't vanish because you cleaned up locally.
17
-
18
- ```
19
- .figs/
20
- config.json # { endpoint, workspaceId, agentId } — written by `figs init` (commit it)
21
- agent.json # who you are: your charter/spine — you write this (commit it)
22
- runs.jsonl # what you did, one line per run — you append (gitignored)
23
- asks.jsonl # what you need from a human — you append (gitignored)
24
- artifacts/ # the reports you produced — you copy in (gitignored)
25
- GUIDE.md # this file
26
- ```
27
-
28
- **Commit `config.json` + `agent.json`** (your identity + charter, non-secret). The activity
29
- files are a transient outbox — `figs init` gitignores them; the server aggregates them.
30
-
31
- ## `agent.json` — your charter (the spine)
32
-
33
- Write this by reading **your own repo** — your `CLAUDE.md` / `MEMORY.md` already say who you
34
- are. Derive it, don't invent it, and keep it current as your role changes. **Do not put an
35
- `id` here** — your identity UUID lives in `config.json` and the CLI attaches it on push.
36
-
37
- | Field | Req | What it is |
38
- |---|---|---|
39
- | `name` | ✅ | Display name (e.g. "Reconciliation"). |
40
- | `type` | | `"agent"` (default) or `"human"`. |
41
- | `role` | | One-line title (bilingual is fine). |
42
- | `status` | | Free text — your current state (e.g. `"in_dev"`, `"healthy"`). |
43
- | `mandate` | | **Your 工作執掌** — one sentence: what you're accountable for. Shown loudest. |
44
- | `avatar` | | `{ "seed": "<string>" }` — seeds your avatar. |
45
- | `org` | | `{ "department": "...", "manager": "<member email>" }`. **`department` groups you in the org chart.** |
46
- | `runtime` | | e.g. `"Claude Code"`. |
47
- | `cadence` | | e.g. `"Monthly"`, `"Quarterly"`. |
48
- | `method` | | `string[]` — **how you work**, numbered steps. The "How it works" section. |
49
- | `properties` | | `[{ "k": "...", "v": "..." }]` — free-form facts shown on your card. |
50
- | `units` | | `[]` — the things you're responsible for (a customer, a job). Optional; omit if none. |
51
-
52
- **A `unit`:** `{ id, name, subtitle?, status?, period?, detail?, stats?: [{l,v}] }`. The `id`
53
- is how your runs link to it (a run's `unit` matches a unit `id`).
54
-
55
- ```json
56
- {
57
- "name": "Reconciliation",
58
- "type": "agent",
59
- "role": "對帳專員 · Reconciliation Officer",
60
- "status": "in_dev",
61
- "avatar": { "seed": "Reconciliation" },
62
- "org": { "department": "Finance Ops · CSR", "manager": "you@company.com" },
63
- "runtime": "Claude Code",
64
- "cadence": "Monthly",
65
- "mandate": "Reconciles open invoices every month — flags what doesn't match for review.",
66
- "method": [
67
- "Pull the WT-side open invoices and the customer-side statement for the month.",
68
- "Match on PO / delivery-number keys within tolerance.",
69
- "Classify every key — matched / needs-review / WT-only / cust-only — with a 'why'.",
70
- "Surface discrepancies. Never write back to the source."
71
- ],
72
- "properties": [{ "k": "Department", "v": "Finance Ops · CSR" }],
73
- "units": [
74
- {
75
- "id": "compal", "name": "Compal", "subtitle": "仁寶電腦",
76
- "status": "88% matched · 31 keys flagged", "period": "2025-11",
77
- "stats": [{ "l": "Matched", "v": "2,161 keys" }, { "l": "Needs review", "v": "31 keys" }]
78
- }
79
- ]
80
- }
81
- ```
82
-
83
- ## `runs.jsonl` — what you did (append one line per run)
84
-
85
- ```json
86
- { "id": "compal-2025-11", "ts": "2026-05-28T23:41:26Z", "unit": "compal", "period": "2025-11", "result": "88% matched · 31 keys flagged", "status": "ok", "artifact": "compal-2025-11.html" }
87
- ```
88
-
89
- - `id` ✅ and `ts` ✅ (ISO-8601 with offset) are required. `status`: `ok | warn | fail` (default `ok`).
90
- - `unit` links to a unit `id`. `result` is the one-line outcome. `artifact` is a file in `artifacts/`.
91
- - **Idempotent by `id`** — re-pushing the same id updates that run, never duplicates. Use a stable id.
92
-
93
- ## `asks.jsonl` — what you need from a human (append)
94
-
95
- Raise your hand when you're stuck. Assemble the full context so the human can act without
96
- re-gathering anything.
97
-
98
- ```json
99
- {
100
- "id": "quanta-bridge", "ts": "2026-05-28T21:05:00Z",
101
- "type": "confirm-assumption", "status": "open", "unit": "quanta",
102
- "title": "No bridge rule for prefixed invoice numbers",
103
- "found": "~180 rows can't be matched safely; guessing risks false matches.",
104
- "need": "Confirm the bridge rule for prefixed invoice numbers.",
105
- "options": ["Strip the alpha prefix", "Use a mapping you provide", "Treat as out-of-scope"],
106
- "details": [ { "l": "Amount at risk", "v": "$52.9M" } ],
107
- "refs": [ { "label": "Quanta report", "artifact": "quanta-2025-11.html" } ]
108
- }
109
- ```
110
-
111
- - Required: `id`, `type`, `title`. `type`: `blocked | needs-decision | confirm-assumption | sign-off`.
112
- - Optional context (each renders only if present): `found`, `need`, `options[]`, `details[]`, `refs[]`.
113
- - **You own the lifecycle.** Close an ask by appending `{ "id": "quanta-bridge", "status": "resolved" }`
114
- on a later run. Folded by `id`. Strictly one-way — the human acts in their own workflow.
115
-
116
- ## `artifacts/` — your reports
117
-
118
- Drop the report a run produced here and point to it from the run's `artifact` (filename only).
119
- Supported: **`.html` `.md` `.txt` `.json`** and images (`.png .jpg .gif .webp .svg`), **≤ 3 MB**
120
- (compress larger images). HTML/markdown render in a sandboxed viewer; the file is shown
121
- exactly as you produced it.
122
-
123
- ## Lifecycle
124
-
125
- **One-time setup** (per repo):
126
-
127
- 1. **Log in** *(human step — a token is a secret)*: run `figs login`; your human opens the
128
- printed link, signs in, and clicks **Approve**. (Fallback: `figs login <token>`.)
129
- 2. **`figs init --workspace <id>`** — generates your identity UUID and writes `config.json`
130
- + `.gitignore` + this `GUIDE.md`. Get the workspace id from `figs workspaces`.
131
- 3. **Author `agent.json`** from your own `CLAUDE.md` / `MEMORY.md`, then **`figs doctor`**.
132
- 4. **`figs push`** — you now appear in the org chart, self-described.
133
-
134
- **Every run** (wire this into your run loop, not a one-off):
135
-
136
- 1. Append a line to `runs.jsonl`; record any blockers in `asks.jsonl`.
137
- 2. Copy the run's report into `artifacts/`.
138
- 3. `figs push`.
139
-
140
- ## Rules
141
-
142
- - **One-way, never deletes.** You publish; Figs mirrors. Deleting locally doesn't delete remote.
143
- - **You own your identity.** The UUID in `config.json` is yours — commit it so everyone running
144
- this repo pushes to the *same* you.
145
- - **Idempotent.** Re-running `figs push` is always safe; records fold by `id`.
146
- - **The token is the human's job.** Never enter or generate auth tokens yourself.
147
- - **Keep your charter honest.** Update `agent.json` when what you do changes, then `figs doctor`.