@figs-so/cli 1.0.0 → 1.1.0
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/SPEC.md +8 -2
- package/figs.mjs +27 -3
- package/package.json +1 -1
package/SPEC.md
CHANGED
|
@@ -259,7 +259,8 @@ minimum CLI version requires Bearer.)
|
|
|
259
259
|
"agent": { /* agent.json */ },
|
|
260
260
|
"runs": [ /* runs.jsonl */ ], // optional
|
|
261
261
|
"asks": [ /* asks.jsonl */ ], // optional
|
|
262
|
-
"messages": [ /* messages.jsonl */ ]
|
|
262
|
+
"messages": [ /* messages.jsonl */ ], // optional — transcribed replies the reader lacks
|
|
263
|
+
"confirmRename": true // optional — `figs push --rename`: confirm a real name change
|
|
263
264
|
}
|
|
264
265
|
```
|
|
265
266
|
2. **Each attached file** → `POST {endpoint}/api/artifacts/upload`, base64-encoded, hash-verified.
|
|
@@ -270,7 +271,12 @@ server refuses a fold older than the record's stored close/settle (a stale machi
|
|
|
270
271
|
and accepts a newer one (a legitimate reopen — the `warn` → `ok` evolution). **A push never re-homes an
|
|
271
272
|
agent:** a `workspaceId` differing from the agent's registered home is rejected `409`
|
|
272
273
|
`{ "error", "code": "agent_moved", "workspaceId"? }`; the agent recovers by setting
|
|
273
|
-
`config.json#workspaceId` to the named workspace and pushing again.
|
|
274
|
+
`config.json#workspaceId` to the named workspace and pushing again. **A push never silently
|
|
275
|
+
re-identifies an agent:** a `name` differing from the one registered for that `agentId` is the
|
|
276
|
+
fingerprint of a copied folder and is rejected `409` `{ "error", "code": "agent_renamed" }` — the
|
|
277
|
+
agent rotates identity (`rm -rf .figs && figs init`) for a genuinely new agent, or sets
|
|
278
|
+
`confirmRename` (`figs push --rename`) once to confirm a real rename. `name` is the signal because a
|
|
279
|
+
copy-to-make-another always renames while role/mandate evolve legitimately; the check is `name` only.
|
|
274
280
|
|
|
275
281
|
**Down — the reply sync.** Delivery is **agent-pulled**, never pushed into the repo: a reader exposes a
|
|
276
282
|
read returning **this agent's human messages** (answers/verdicts), which the CLI merges into
|
package/figs.mjs
CHANGED
|
@@ -279,12 +279,16 @@ const COMMANDS = {
|
|
|
279
279
|
},
|
|
280
280
|
push: {
|
|
281
281
|
args: "",
|
|
282
|
-
flags: [],
|
|
282
|
+
flags: ["--rename"],
|
|
283
283
|
desc: "publish .figs/ — spine to /api/ingest, artifacts to /api/artifacts",
|
|
284
284
|
more: [
|
|
285
285
|
"Idempotent (records fold by id). Exits non-zero if an artifact upload is rejected.",
|
|
286
286
|
"The writing verbs (report/ask/resolve) call this automatically — you only need it",
|
|
287
287
|
"after hand-editing files, after --no-push, or to retry a failed auto-push.",
|
|
288
|
+
"--rename: confirm a genuine name change on an already-registered agent (one",
|
|
289
|
+
"time). The server refuses a name that doesn't match the registered one — it's",
|
|
290
|
+
"the fingerprint of a copied folder; if that's what happened, rotate identity",
|
|
291
|
+
"instead with `rm -rf .figs && figs init`, don't --rename.",
|
|
288
292
|
],
|
|
289
293
|
eg: "figs push",
|
|
290
294
|
},
|
|
@@ -2135,6 +2139,9 @@ async function doPush() {
|
|
|
2135
2139
|
const asks = foldById(readJsonl("asks.jsonl"))
|
|
2136
2140
|
// Messages are immutable events — sent whole (no fold); the server dedupes by id.
|
|
2137
2141
|
const messages = readJsonl("messages.jsonl")
|
|
2142
|
+
// One-time confirm that a name change on an already-registered id is a real
|
|
2143
|
+
// rename, not a copied folder (the server's rename guard refuses it otherwise).
|
|
2144
|
+
const confirmRename = hasFlag("--rename")
|
|
2138
2145
|
|
|
2139
2146
|
// Local pre-flight — fail fast, offline, with teaching errors.
|
|
2140
2147
|
const placeholders = findPlaceholders(agentJson)
|
|
@@ -2152,7 +2159,14 @@ async function doPush() {
|
|
|
2152
2159
|
res = await fetchT(`${base}/api/ingest`, {
|
|
2153
2160
|
method: "POST",
|
|
2154
2161
|
headers: { "content-type": "application/json", ...authHeaders(token) },
|
|
2155
|
-
body: JSON.stringify({
|
|
2162
|
+
body: JSON.stringify({
|
|
2163
|
+
workspaceId: config.workspaceId,
|
|
2164
|
+
agent,
|
|
2165
|
+
runs,
|
|
2166
|
+
asks,
|
|
2167
|
+
messages,
|
|
2168
|
+
...(confirmRename ? { confirmRename: true } : {}),
|
|
2169
|
+
}),
|
|
2156
2170
|
})
|
|
2157
2171
|
} catch (e) {
|
|
2158
2172
|
// Network/timeout — transient; the records are safe locally.
|
|
@@ -2160,7 +2174,17 @@ async function doPush() {
|
|
|
2160
2174
|
}
|
|
2161
2175
|
const text = await res.text()
|
|
2162
2176
|
// 4xx = the payload is wrong (re-pushing won't help) → structural; 5xx = transient.
|
|
2163
|
-
|
|
2177
|
+
// The server's teaching message rides in { error } — surface that, not raw JSON.
|
|
2178
|
+
if (!res.ok) {
|
|
2179
|
+
let detail = text
|
|
2180
|
+
try {
|
|
2181
|
+
const body = JSON.parse(text)
|
|
2182
|
+
if (body?.error) detail = body.error
|
|
2183
|
+
} catch {
|
|
2184
|
+
// non-JSON body — keep the raw text
|
|
2185
|
+
}
|
|
2186
|
+
return fail(`server rejected it (${res.status}): ${detail}`, res.status >= 500)
|
|
2187
|
+
}
|
|
2164
2188
|
console.log(
|
|
2165
2189
|
`figs: ✓ pushed ${agent.name ?? agent.id} — ${runs.length} runs, ${asks.length} asks, ${messages.length} messages`,
|
|
2166
2190
|
)
|