@figs-so/cli 0.1.3 → 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/figs.mjs +55 -28
- package/package.json +1 -1
package/figs.mjs
CHANGED
|
@@ -31,7 +31,7 @@ import { homedir } from "node:os"
|
|
|
31
31
|
import { join } from "node:path"
|
|
32
32
|
import { randomUUID } from "node:crypto"
|
|
33
33
|
|
|
34
|
-
const VERSION = "0.1.
|
|
34
|
+
const VERSION = "0.1.5"
|
|
35
35
|
// Going-forward default; override with FIGS_ENDPOINT or .figs/config.json endpoint
|
|
36
36
|
// (e.g. FIGS_ENDPOINT=http://localhost:3000 for local dev).
|
|
37
37
|
const DEFAULT_ENDPOINT = "https://app.figs.so"
|
|
@@ -197,6 +197,10 @@ async function login(token) {
|
|
|
197
197
|
}
|
|
198
198
|
if (status === "denied") die("authorization denied")
|
|
199
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")
|
|
200
204
|
// pending → keep polling
|
|
201
205
|
}
|
|
202
206
|
die("timed out waiting for approval — run `figs login` again")
|
|
@@ -207,6 +211,7 @@ async function status() {
|
|
|
207
211
|
const token = getToken()
|
|
208
212
|
const cfg = readJson(join(repoDir, "config.json"), null)
|
|
209
213
|
const hasAgent = existsSync(join(repoDir, "agent.json"))
|
|
214
|
+
const hasContract = existsSync(join(repoDir, "CONTRACT.md"))
|
|
210
215
|
const endpoint = resolveEndpoint()
|
|
211
216
|
|
|
212
217
|
let loggedIn = false
|
|
@@ -229,6 +234,7 @@ async function status() {
|
|
|
229
234
|
workspaces: list?.map((w) => ({ id: w.id, name: w.name, role: w.role })),
|
|
230
235
|
config: cfg ? { workspaceId: cfg.workspaceId, agentId: cfg.agentId } : null,
|
|
231
236
|
agentJson: hasAgent,
|
|
237
|
+
contractMd: hasContract,
|
|
232
238
|
},
|
|
233
239
|
null,
|
|
234
240
|
2,
|
|
@@ -255,7 +261,13 @@ async function status() {
|
|
|
255
261
|
? cfg.workspaceId
|
|
256
262
|
: "not initialized — run `figs init --workspace <id>`",
|
|
257
263
|
)
|
|
258
|
-
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
|
+
)
|
|
259
271
|
row("endpoint", endpoint)
|
|
260
272
|
row("cli", VERSION)
|
|
261
273
|
}
|
|
@@ -286,31 +298,30 @@ function isUuid(s) {
|
|
|
286
298
|
}
|
|
287
299
|
|
|
288
300
|
/**
|
|
289
|
-
* The local `.figs/GUIDE.md` is a thin
|
|
290
|
-
* always-current guide +
|
|
291
|
-
*
|
|
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).
|
|
292
305
|
*/
|
|
293
306
|
function guideStub(endpoint) {
|
|
294
|
-
return `<!-- Written by \`figs init
|
|
295
|
-
|
|
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.
|
|
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
|
|
300
310
|
|
|
301
|
-
|
|
311
|
+
This repo publishes to **Figs** (your manager's read-only window into what you do).
|
|
302
312
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
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.
|
|
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).
|
|
308
316
|
|
|
309
|
-
|
|
310
|
-
|
|
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.
|
|
311
322
|
|
|
312
|
-
Commit config.json + agent.json
|
|
313
|
-
|
|
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.
|
|
314
325
|
`
|
|
315
326
|
}
|
|
316
327
|
|
|
@@ -355,7 +366,7 @@ async function init() {
|
|
|
355
366
|
writeFileSync(
|
|
356
367
|
giPath,
|
|
357
368
|
[
|
|
358
|
-
"# Figs — commit config.json + agent.json
|
|
369
|
+
"# Figs — commit config.json + agent.json + CONTRACT.md + GUIDE.md.",
|
|
359
370
|
"# Activity is a transient outbox: emitted per run, aggregated remotely.",
|
|
360
371
|
"runs.jsonl",
|
|
361
372
|
"asks.jsonl",
|
|
@@ -371,8 +382,9 @@ async function init() {
|
|
|
371
382
|
`figs: ✓ .figs/config.json + .gitignore + GUIDE.md written (agentId ${agentId})`,
|
|
372
383
|
)
|
|
373
384
|
console.log(
|
|
374
|
-
`
|
|
385
|
+
` Phase 1: author .figs/agent.json (your charter), then \`figs doctor\` && \`figs push\` to appear.`,
|
|
375
386
|
)
|
|
387
|
+
console.log(` Full guide: ${endpoint}/llms.txt`)
|
|
376
388
|
}
|
|
377
389
|
|
|
378
390
|
/** Validate the local .figs/ payload against the contract — no write. */
|
|
@@ -445,8 +457,10 @@ async function push() {
|
|
|
445
457
|
* The spine ingest is JSON-only; artifacts go to a separate endpoint that stores
|
|
446
458
|
* them content-addressed (an unchanged file is skipped server-side). Content is
|
|
447
459
|
* sent base64-encoded so any type — html, markdown, text, json, images — survives.
|
|
448
|
-
*
|
|
449
|
-
*
|
|
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.
|
|
450
464
|
*/
|
|
451
465
|
async function pushArtifacts(base, token, config, runs, asks) {
|
|
452
466
|
const refNames = (asks ?? []).flatMap((a) =>
|
|
@@ -459,10 +473,13 @@ async function pushArtifacts(base, token, config, runs, asks) {
|
|
|
459
473
|
|
|
460
474
|
let uploaded = 0
|
|
461
475
|
let unchanged = 0
|
|
476
|
+
let missing = 0
|
|
477
|
+
let failed = 0
|
|
462
478
|
for (const name of names) {
|
|
463
479
|
const p = join(repoDir, "artifacts", name)
|
|
464
480
|
if (!existsSync(p)) {
|
|
465
481
|
console.warn(`figs: ! artifact missing, skipped: artifacts/${name}`)
|
|
482
|
+
missing++
|
|
466
483
|
continue
|
|
467
484
|
}
|
|
468
485
|
const content = readFileSync(p).toString("base64")
|
|
@@ -477,8 +494,13 @@ async function pushArtifacts(base, token, config, runs, asks) {
|
|
|
477
494
|
}),
|
|
478
495
|
})
|
|
479
496
|
if (!res.ok) {
|
|
480
|
-
const t = await res.text()
|
|
481
|
-
|
|
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++
|
|
482
504
|
continue
|
|
483
505
|
}
|
|
484
506
|
const body = await res.json().catch(() => ({}))
|
|
@@ -486,8 +508,13 @@ async function pushArtifacts(base, token, config, runs, asks) {
|
|
|
486
508
|
else uploaded++
|
|
487
509
|
}
|
|
488
510
|
console.log(
|
|
489
|
-
`figs: ✓ artifacts — ${uploaded} uploaded, ${unchanged} unchanged
|
|
511
|
+
`figs: ${failed ? "✗" : "✓"} artifacts — ${uploaded} uploaded, ${unchanged} unchanged` +
|
|
512
|
+
(missing ? `, ${missing} missing` : "") +
|
|
513
|
+
(failed ? `, ${failed} failed` : ""),
|
|
490
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)
|
|
491
518
|
}
|
|
492
519
|
|
|
493
520
|
function readJsonl(name) {
|