@figs-so/cli 0.1.1 → 0.1.3

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,16 +28,13 @@ 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.1"
34
+ const VERSION = "0.1.3"
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
- const DEFAULT_ENDPOINT = "https://figs.so"
37
+ const DEFAULT_ENDPOINT = "https://app.figs.so"
41
38
 
42
39
  const repoDir = join(process.cwd(), ".figs")
43
40
  const globalDir = join(homedir(), ".figs")
@@ -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") {
@@ -263,40 +260,88 @@ async function status() {
263
260
  row("cli", VERSION)
264
261
  }
265
262
 
266
- /** List the user's workspaces, or create one with --create "<name>". */
263
+ /**
264
+ * List the user's workspaces (read-only). Creating a workspace is a human,
265
+ * web-side action — the agent only ever joins one it was pointed at. Surfaces
266
+ * the slug, which is what `figs init --workspace <slug>` takes.
267
+ */
267
268
  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
269
  const { workspaces: list = [] } = await api("GET", "/api/workspaces")
281
270
  if (JSON_OUT) return void console.log(JSON.stringify(list, null, 2))
282
271
  if (list.length === 0) {
283
- console.log('figs: no workspaces yet — `figs workspaces --create "<name>"`')
272
+ console.log("figs: no workspaces yet — create one in the Figs web app, then re-run.")
284
273
  return
285
274
  }
286
275
  console.log(`figs: ${list.length} workspace${list.length === 1 ? "" : "s"}`)
287
276
  for (const w of list) {
288
- console.log(` ${String(w.role).padEnd(6)} ${w.name} ${w.id}`)
277
+ console.log(` ${String(w.role).padEnd(6)} ${w.slug} ${w.name}`)
289
278
  }
279
+ console.log(" → figs init --workspace <slug>")
290
280
  }
291
281
 
292
- function init() {
293
- const workspaceId = flag("--workspace")
294
- if (!workspaceId) {
295
- die("usage: figs init --workspace <workspaceId> [--endpoint <url>]")
282
+ function isUuid(s) {
283
+ return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(
284
+ String(s),
285
+ )
286
+ }
287
+
288
+ /**
289
+ * The local `.figs/GUIDE.md` is a thin, endpoint-relative pointer — the full,
290
+ * always-current guide + `agent.json` schema is served at `${endpoint}/llms.txt`
291
+ * (one source, no frozen copy to drift). Just enough here to act.
292
+ */
293
+ function guideStub(endpoint) {
294
+ return `<!-- Written by \`figs init\`. Full, always-current guide + schema: ${endpoint}/llms.txt -->
295
+ # Figs — quickstart for this agent
296
+
297
+ You are an AI employee. **Figs** is the read-only window your manager uses to see what you're
298
+ responsible for, what you've done, and what you need from them. You publish to it; it never
299
+ writes back into this repo.
300
+
301
+ **Full guide + \`agent.json\` schema → ${endpoint}/llms.txt** — read this before authoring your charter.
302
+
303
+ Lifecycle:
304
+ 1. \`figs login\` a human approves in a browser — the token is theirs, never yours.
305
+ 2. \`figs init --workspace <slug>\` done — wrote config.json (your identity UUID) + this file.
306
+ 3. author \`.figs/agent.json\` derive it from this repo's CLAUDE.md / MEMORY.md, then \`figs doctor\`.
307
+ 4. \`figs push\` you now appear in the org chart, self-described.
308
+
309
+ Every run (wire this into your run loop, not a one-off):
310
+ append a line to runs.jsonl · record blockers in asks.jsonl · copy reports into artifacts/ · \`figs push\`.
311
+
312
+ Commit config.json + agent.json (identity + charter). runs.jsonl / asks.jsonl / artifacts/ are a
313
+ transient outbox — gitignored, aggregated remotely. The sync is one-way and never deletes on the server.
314
+ `
315
+ }
316
+
317
+ async function init() {
318
+ const workspaceArg = flag("--workspace")
319
+ if (!workspaceArg) {
320
+ die("usage: figs init --workspace <slug-or-id> [--endpoint <url>]")
296
321
  }
322
+ const endpoint = (flag("--endpoint") || resolveEndpoint()).replace(/\/+$/, "")
323
+
324
+ // A UUID is the canonical workspace id (used as-is, no network). A slug is the
325
+ // human-friendly handle — resolve it to its UUID via the API, and store the
326
+ // UUID so a later workspace rename can't break this agent's pushes.
327
+ let workspaceId = workspaceArg
328
+ if (!isUuid(workspaceArg)) {
329
+ if (!getToken()) {
330
+ die("not logged in — run `figs login` first (resolving a workspace slug needs auth; or pass the workspace UUID)")
331
+ }
332
+ const r = await request("GET", "/api/workspaces", null, getToken())
333
+ if (!r.ok) {
334
+ die(`could not resolve workspace "${workspaceArg}" (${r.status}): ${r.data.error ?? r.data.raw ?? ""}`)
335
+ }
336
+ const list = r.data.workspaces ?? []
337
+ const match = list.find((w) => w.slug === workspaceArg || w.id === workspaceArg)
338
+ if (!match) {
339
+ die(`no workspace matching "${workspaceArg}" — run \`figs workspaces\` to see yours`)
340
+ }
341
+ workspaceId = match.id
342
+ }
343
+
297
344
  const existing = readJson(join(repoDir, "config.json"), null)
298
- const endpoint =
299
- flag("--endpoint") || existing?.endpoint || "http://localhost:3000"
300
345
  const agentId = existing?.agentId || randomUUID()
301
346
  mkdirSync(repoDir, { recursive: true })
302
347
  writeFileSync(
@@ -320,22 +365,14 @@ function init() {
320
365
  ].join("\n"),
321
366
  )
322
367
  }
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
- }
368
+ writeFileSync(join(repoDir, "GUIDE.md"), guideStub(endpoint))
334
369
 
335
370
  console.log(
336
- `figs: ✓ .figs/config.json + .gitignore${guide} written (agentId ${agentId})`,
371
+ `figs: ✓ .figs/config.json + .gitignore + GUIDE.md written (agentId ${agentId})`,
372
+ )
373
+ console.log(
374
+ ` read .figs/GUIDE.md (full guide: ${endpoint}/llms.txt), author .figs/agent.json, then \`figs doctor\`.`,
337
375
  )
338
- console.log(" read .figs/GUIDE.md, then author .figs/agent.json and run `figs doctor`.")
339
376
  }
340
377
 
341
378
  /** Validate the local .figs/ payload against the contract — no write. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@figs-so/cli",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
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`.