run402 4.43.0 → 4.44.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/lib/repos.mjs +112 -13
- package/package.json +1 -1
package/lib/repos.mjs
CHANGED
|
@@ -33,7 +33,7 @@ import { resolveGitvaultTarget } from "./gitvault-target.mjs";
|
|
|
33
33
|
import { nextAction, claimOrgSlugAction, claimRepoNameAction } from "./next-actions.mjs";
|
|
34
34
|
import { printKeystoreLocation } from "./gitvault.mjs";
|
|
35
35
|
import { gitvaultRemoteUrlForRepo } from "#sdk";
|
|
36
|
-
import { sdkStats, printVerboseStats } from "./stats.mjs";
|
|
36
|
+
import { sdkStats, printVerboseStats, isVerbose } from "./stats.mjs";
|
|
37
37
|
import {
|
|
38
38
|
normalizeArgv,
|
|
39
39
|
hasHelp,
|
|
@@ -63,7 +63,7 @@ Then plain git, forever:
|
|
|
63
63
|
git clone run402::<org>/<repo>
|
|
64
64
|
|
|
65
65
|
Occasional:
|
|
66
|
-
run402 repos snapshot [--project <id>] [--repo <repo_id>] [--message <text>] [--checkpoint] [--dry-run] [--allow-dirty]
|
|
66
|
+
run402 repos snapshot [--project <id>] [--repo <repo_id>] [--message <text>] [--checkpoint] [--dry-run] [--allow-dirty] [--manifest-out <path>]
|
|
67
67
|
run402 repos mirror [<destination>] [--off] [--backfill] [--profile <name> | --ambient] [--region <r>] [--endpoint <url>] [--project <id>] [--repo <repo_id>]
|
|
68
68
|
run402 repos recover <source> --out <dir> [--repo <repo_id>] [--profile <name> | --ambient] [--region <r>] [--endpoint <url>] [--human]
|
|
69
69
|
|
|
@@ -134,6 +134,15 @@ Subcommands:
|
|
|
134
134
|
capture it as-is; the result then discloses exactly what was
|
|
135
135
|
swept in (modified_captured / untracked_captured), printed to
|
|
136
136
|
stderr too. \`--dry-run\` surfaces the same refusal.
|
|
137
|
+
Both \`--dry-run\` and a real snapshot print a SUMMARY by default —
|
|
138
|
+
file counts (files_total/files_changed/files_new), total/delta
|
|
139
|
+
bytes, and up to 200 changed/new paths (changed_more names any
|
|
140
|
+
overflow) — never the full captured-file inventory, which can run
|
|
141
|
+
to thousands of entries on a real repo. \`--manifest-out <path>\`
|
|
142
|
+
writes the complete inventory to a file (the result's
|
|
143
|
+
manifest_path names it); \`-v\`/\`--verbose\` inlines the full
|
|
144
|
+
inventory directly in the JSON (in addition to its usual stderr
|
|
145
|
+
stats line, not instead of it).
|
|
137
146
|
mirror ONE flag-driven verb for the client-side, customer-
|
|
138
147
|
owned ciphertext mirror — run402 never holds a credential to it.
|
|
139
148
|
No argument: READ the configured destination + a keyless
|
|
@@ -145,12 +154,16 @@ Subcommands:
|
|
|
145
154
|
mirror that fell behind). Exactly one of these per call. Mirror
|
|
146
155
|
state also renders inside \`repos view\`; mirror INTEGRITY inside
|
|
147
156
|
\`repos fsck --mirror\`.
|
|
148
|
-
recover \`r402s-recover\`: rebuild a
|
|
149
|
-
a mirrored prefix, with NO SERVER INVOLVED —
|
|
150
|
-
path (normal retrieval is plain \`git clone
|
|
151
|
-
no \`repos clone\` verb exists).
|
|
152
|
-
|
|
153
|
-
|
|
157
|
+
recover \`r402s-recover\`: rebuild a BARE recovery repository (no working
|
|
158
|
+
files) straight from a mirrored prefix, with NO SERVER INVOLVED —
|
|
159
|
+
the offline disaster path (normal retrieval is plain \`git clone
|
|
160
|
+
run402::<org>/<repo>\`, no \`repos clone\` verb exists). The result's
|
|
161
|
+
\`layout\` is \`"bare"\` and its \`next_actions\` print the exact
|
|
162
|
+
\`git clone <out_dir> <out_dir>-worktree\` to run for a working
|
|
163
|
+
tree — recover itself never checks files out. Proves this
|
|
164
|
+
mirror's validity, never freshness — read both honesty statements
|
|
165
|
+
before relying on the result. \`--human\` renders a short summary
|
|
166
|
+
instead of JSON.
|
|
154
167
|
fsck Walks the head chain AND materializes
|
|
155
168
|
the ref map, advancing BOTH
|
|
156
169
|
local trust pins — reported EXPLICITLY as local_state_changed +
|
|
@@ -181,7 +194,11 @@ Subcommands:
|
|
|
181
194
|
has NOT yet been rotated away — pending_removal is honest
|
|
182
195
|
bookkeeping, not enforcement, until \`access repair\`/\`revoke-key\`
|
|
183
196
|
actually rotates. history_scope (which epochs each recipient can
|
|
184
|
-
read) is not reported by this read — see the \`gap\` field.
|
|
197
|
+
read) is not reported by this read — see the \`gap\` field. An
|
|
198
|
+
enrolled teammate's key envelope is wrapped AUTOMATICALLY — no
|
|
199
|
+
manual step — by the next \`git push\` or \`repos snapshot\` any
|
|
200
|
+
key-holding client runs (best-effort, non-blocking; the retired
|
|
201
|
+
\`gitvault reconcile\` verb did this by hand and is REMOVED).
|
|
185
202
|
\`--human\` renders a compact roster instead of JSON (the read
|
|
186
203
|
form only — repair/revoke-key/declare-exposure stay JSON-only).
|
|
187
204
|
access repair
|
|
@@ -252,6 +269,12 @@ Options:
|
|
|
252
269
|
The result discloses exactly what was swept in
|
|
253
270
|
(modified_captured / untracked_captured) — even this
|
|
254
271
|
override never captures silently.
|
|
272
|
+
--manifest-out <path>
|
|
273
|
+
snapshot: write the complete captured-file inventory
|
|
274
|
+
(the full JSON the SDK returned, untouched) to a private
|
|
275
|
+
0600 file instead of stdout's default summary. The
|
|
276
|
+
printed result's manifest_path names it. Composes with
|
|
277
|
+
--dry-run and with -v/--verbose.
|
|
255
278
|
--off mirror: remove the configured destination (config only)
|
|
256
279
|
--backfill mirror: copy every object the configured mirror is missing
|
|
257
280
|
--profile <name> mirror / recover: the AWS credential profile name for an
|
|
@@ -283,7 +306,10 @@ Options:
|
|
|
283
306
|
-v, --verbose Print one stderr summary line of this call's request
|
|
284
307
|
stats (round trips, wire time, bytes). Coexists with
|
|
285
308
|
--human. The JSON result always carries a \`stats\` block
|
|
286
|
-
regardless of this flag.
|
|
309
|
+
regardless of this flag. On \`snapshot\`/\`snapshot
|
|
310
|
+
--dry-run\`, ALSO inlines the full captured-file
|
|
311
|
+
inventory in stdout's JSON (composes with the stats
|
|
312
|
+
line — both happen, not one or the other).
|
|
287
313
|
--json No-op: stdout is already JSON.
|
|
288
314
|
|
|
289
315
|
Terminal loss (protocol §0):
|
|
@@ -300,6 +326,7 @@ Examples:
|
|
|
300
326
|
run402 repos list --org org_1a2b3c --human
|
|
301
327
|
run402 repos rename my-notes --project prj_1a2b3c
|
|
302
328
|
run402 repos snapshot --dry-run
|
|
329
|
+
run402 repos snapshot --dry-run --manifest-out /tmp/snapshot-plan.json
|
|
303
330
|
run402 repos snapshot --allow-dirty
|
|
304
331
|
run402 repos mirror s3://acme-vault-mirror --profile acme
|
|
305
332
|
run402 repos mirror --backfill
|
|
@@ -1068,7 +1095,7 @@ async function del(args) {
|
|
|
1068
1095
|
|
|
1069
1096
|
// ─── snapshot ───────────────────────────────────────────────────────────────
|
|
1070
1097
|
|
|
1071
|
-
const SNAPSHOT_VALUE_FLAGS = [...COMMON_VALUE_FLAGS, "--message"];
|
|
1098
|
+
const SNAPSHOT_VALUE_FLAGS = [...COMMON_VALUE_FLAGS, "--message", "--manifest-out"];
|
|
1072
1099
|
|
|
1073
1100
|
/**
|
|
1074
1101
|
* When neither `--repo` nor `--project` was given explicitly, look at the
|
|
@@ -1107,6 +1134,74 @@ function printDirtyDisclosure(snapshot) {
|
|
|
1107
1134
|
for (const p of snapshot.untracked_captured ?? []) console.error(`captured (untracked): ${p}`);
|
|
1108
1135
|
}
|
|
1109
1136
|
|
|
1137
|
+
/** How many `changed_paths` entries {@link summarizeSnapshotPayload} inlines before capping. */
|
|
1138
|
+
const SNAPSHOT_CHANGED_PATHS_CAP = 200;
|
|
1139
|
+
|
|
1140
|
+
/**
|
|
1141
|
+
* item 1 (dogfood): `snapshot.captured` — the SDK's full captured-file
|
|
1142
|
+
* inventory (every tracked + untracked-not-ignored path in the repo, always
|
|
1143
|
+
* populated regardless of how small the actual push delta is) — is a
|
|
1144
|
+
* multi-thousand-line flood on a real repo, even when what actually
|
|
1145
|
+
* publishes is a handful of kilobytes. The SDK keeps returning it in full
|
|
1146
|
+
* (thin-shim law: other SDK consumers may want it) — this reshapes ONLY the
|
|
1147
|
+
* CLI's own stdout, by default:
|
|
1148
|
+
*
|
|
1149
|
+
* - `files_total` / `files_changed` / `files_new` — counts. `files_total`
|
|
1150
|
+
* is `captured.length`; `files_changed`/`files_new` are
|
|
1151
|
+
* `modified_captured`/`untracked_captured` — the ONLY per-path drift
|
|
1152
|
+
* this data distinguishes (the `--allow-dirty` sweep-in disclosure).
|
|
1153
|
+
* On the common clean-tree path both are empty, so `changed_paths` is
|
|
1154
|
+
* too — there is no `files_deleted` here, because `captured` only
|
|
1155
|
+
* lists paths PRESENT on disk today; nothing in this data names which
|
|
1156
|
+
* paths a plain clean push's new commits touched.
|
|
1157
|
+
* - `changed_paths` — `modified_captured` ∪ `untracked_captured`,
|
|
1158
|
+
* sorted, capped at `SNAPSHOT_CHANGED_PATHS_CAP`; `changed_more` names
|
|
1159
|
+
* the overflow explicitly rather than truncating silently.
|
|
1160
|
+
* - `snapshot.captured` / `.paths` / `.modified_captured` /
|
|
1161
|
+
* `.untracked_captured` are dropped from the default `snapshot` object
|
|
1162
|
+
* (its other scalar fields — kind, oid, tree_oid, head, head_oid,
|
|
1163
|
+
* captured_digest, top_level, global_excludes_path — stay). `verbose`
|
|
1164
|
+
* restores them (composes with the summary fields, does not replace
|
|
1165
|
+
* them) — the `-v`/`--verbose` flag already means "print a stats
|
|
1166
|
+
* line"; on `snapshot --dry-run`/`snapshot` it ALSO inlines the full
|
|
1167
|
+
* inventory.
|
|
1168
|
+
* - `manifest_path` is `null` unless `--manifest-out <path>` wrote the
|
|
1169
|
+
* COMPLETE, untouched payload to that file — see `writeManifestOut`.
|
|
1170
|
+
*/
|
|
1171
|
+
function summarizeSnapshotPayload(payload, { verbose = false, manifestPath = null } = {}) {
|
|
1172
|
+
const out = { ...payload, manifest_path: manifestPath };
|
|
1173
|
+
const snapshot = payload.snapshot;
|
|
1174
|
+
if (!snapshot) return out;
|
|
1175
|
+
const modified = snapshot.modified_captured ?? [];
|
|
1176
|
+
const untracked = snapshot.untracked_captured ?? [];
|
|
1177
|
+
const changedAll = [...modified, ...untracked].sort();
|
|
1178
|
+
const changedPaths = changedAll.slice(0, SNAPSHOT_CHANGED_PATHS_CAP);
|
|
1179
|
+
out.files_total = Array.isArray(snapshot.captured) ? snapshot.captured.length : 0;
|
|
1180
|
+
out.files_changed = modified.length;
|
|
1181
|
+
out.files_new = untracked.length;
|
|
1182
|
+
out.changed_paths = changedPaths;
|
|
1183
|
+
out.changed_more = changedAll.length - changedPaths.length;
|
|
1184
|
+
if (!verbose) {
|
|
1185
|
+
const { captured, paths, modified_captured, untracked_captured, ...trimmedSnapshot } = snapshot;
|
|
1186
|
+
out.snapshot = trimmedSnapshot;
|
|
1187
|
+
}
|
|
1188
|
+
return out;
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
/** `--manifest-out <path>`: write the COMPLETE, untouched plan/push payload — the full captured-file inventory included — to a private 0600 file. */
|
|
1192
|
+
function writeManifestOut(path, payload) {
|
|
1193
|
+
try {
|
|
1194
|
+
writeFileSync(path, `${JSON.stringify(payload, null, 2)}\n`, { mode: 0o600 });
|
|
1195
|
+
} catch (e) {
|
|
1196
|
+
fail({
|
|
1197
|
+
code: "MANIFEST_OUT_WRITE_FAILED",
|
|
1198
|
+
message: `could not write the full snapshot inventory to ${path}: ${e instanceof Error ? e.message : String(e)}`,
|
|
1199
|
+
hint: "Check that the path is writable and its parent directory exists.",
|
|
1200
|
+
details: { path },
|
|
1201
|
+
});
|
|
1202
|
+
}
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1110
1205
|
async function snapshot(args) {
|
|
1111
1206
|
const a = normalizeArgv(args);
|
|
1112
1207
|
assertKnownFlags(a, [...SNAPSHOT_VALUE_FLAGS, "--checkpoint", "--dry-run", "--allow-dirty", "-v", "--verbose", "--help", "-h"], SNAPSHOT_VALUE_FLAGS);
|
|
@@ -1137,10 +1232,13 @@ async function snapshot(args) {
|
|
|
1137
1232
|
if (allowDirty) snapshotOpts.allowDirty = true;
|
|
1138
1233
|
if (Object.keys(snapshotOpts).length > 0) opts.snapshot = snapshotOpts;
|
|
1139
1234
|
if (a.includes("--checkpoint")) opts.checkpoint = true;
|
|
1235
|
+
const manifestOutPath = flagValue(a, "--manifest-out");
|
|
1236
|
+
const verbose = isVerbose(a);
|
|
1140
1237
|
try {
|
|
1141
1238
|
if (dryRun) {
|
|
1142
1239
|
const plan = await sdk.gitvault.planPush(opts);
|
|
1143
|
-
|
|
1240
|
+
if (manifestOutPath != null) writeManifestOut(manifestOutPath, plan);
|
|
1241
|
+
printJson(sdk, summarizeSnapshotPayload(plan, { verbose, manifestPath: manifestOutPath }));
|
|
1144
1242
|
if (plan.allocation_needed) {
|
|
1145
1243
|
console.error("dry-run: no repo allocated for this project yet — a real snapshot would allocate one first; object/byte sizing is not knowable until then");
|
|
1146
1244
|
} else {
|
|
@@ -1154,7 +1252,8 @@ async function snapshot(args) {
|
|
|
1154
1252
|
return;
|
|
1155
1253
|
}
|
|
1156
1254
|
const result = await sdk.gitvault.push(opts);
|
|
1157
|
-
|
|
1255
|
+
if (manifestOutPath != null) writeManifestOut(manifestOutPath, result);
|
|
1256
|
+
printJson(sdk, summarizeSnapshotPayload(result, { verbose, manifestPath: manifestOutPath }));
|
|
1158
1257
|
console.error(`published generation ${result.generation} (${result.form})`);
|
|
1159
1258
|
if (result.mirror_push?.outcome === "pushed") {
|
|
1160
1259
|
console.error(`mirror: pushed generation ${result.generation} (${result.mirror_push.summary?.objects_copied ?? 0} object(s) copied)`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "run402",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.44.0",
|
|
4
4
|
"description": "CLI for Run402 — full-stack backend infrastructure for AI agents: Postgres, auth, storage, serverless functions and atomic deploys. Paid with x402/MPP. Includes $0.03 image generation.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|