@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 +9 -8
- package/figs.mjs +113 -49
- package/package.json +1 -2
- package/GUIDE.template.md +0 -147
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
|
|
13
|
-
npx @figs-so/cli@latest workspaces
|
|
14
|
-
npx @figs-so/cli@latest init --workspace <
|
|
15
|
-
npx @figs-so/cli@latest doctor
|
|
16
|
-
npx @figs-so/cli@latest push
|
|
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
|
-
|
|
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 [--
|
|
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
|
|
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 {
|
|
32
|
-
import { fileURLToPath } from "node:url"
|
|
31
|
+
import { join } from "node:path"
|
|
33
32
|
import { randomUUID } from "node:crypto"
|
|
34
33
|
|
|
35
|
-
const
|
|
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
|
-
/**
|
|
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(
|
|
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.
|
|
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
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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(
|
|
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
|
-
*
|
|
412
|
-
*
|
|
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
|
-
|
|
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.
|
|
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`.
|