@bitpub/cli 2.0.4 → 2.0.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/package.json +1 -1
- package/src/commands/load.js +81 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bitpub/cli",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.5",
|
|
4
4
|
"description": "BitPub CLI — local-first shared memory for AI agents. Six daily verbs (save/load/list/find/sync/delete), zero-config private namespace, encrypted client-side.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"bitpub": "./bin/bitpub.js"
|
package/src/commands/load.js
CHANGED
|
@@ -12,7 +12,23 @@
|
|
|
12
12
|
* "did you mean…" search across the user's full private namespace.
|
|
13
13
|
* With exactly one match, auto-load and print a transparency note to
|
|
14
14
|
* stderr saying where it actually came from. With multiple, list
|
|
15
|
-
* them and exit. With zero,
|
|
15
|
+
* them and exit. With zero, fall through.
|
|
16
|
+
* 4. If still empty, check whether the address is a *folder* — i.e. it
|
|
17
|
+
* has descendants under it — and if so, auto-load them in one shot.
|
|
18
|
+
* Capped at `--limit` (default 20) so a stray `load` on a deep
|
|
19
|
+
* prefix can't blow up output. A stderr breadcrumb tells the caller
|
|
20
|
+
* what we did and (if truncated) how to get the rest. This catches
|
|
21
|
+
* the common mistake of `load`-ing a prefix like
|
|
22
|
+
* `bitpub://group:foo/Agents/x` when the real slices live at
|
|
23
|
+
* `.../Agents/x/README`, `.../script`, etc. A single wildcard
|
|
24
|
+
* remote pull is attempted on cache miss so this works without a
|
|
25
|
+
* separate `sync` step.
|
|
26
|
+
* 5. Otherwise: actionable not-found error.
|
|
27
|
+
*
|
|
28
|
+
* Multi-slice raw output (from explicit wildcards or from the folder
|
|
29
|
+
* auto-load in step 4) is prefixed with a `=== <address> ===` header
|
|
30
|
+
* per slice so the caller can tell which content came from which slice.
|
|
31
|
+
* Single-slice loads keep their clean stdout-only output (no header).
|
|
16
32
|
*
|
|
17
33
|
* The DWIM step exists because agents holding a fully-qualified URL will
|
|
18
34
|
* sometimes (incorrectly) strip it down to a short name before calling
|
|
@@ -33,6 +49,11 @@ module.exports = function registerLoad(program) {
|
|
|
33
49
|
.description('Load a slice by short name (this project) or full bitpub:// address')
|
|
34
50
|
.option('--no-fetch', 'Do not fetch from cloud if missing locally')
|
|
35
51
|
.option('--format <raw|json>', 'Output format', 'raw')
|
|
52
|
+
.option(
|
|
53
|
+
'--limit <n>',
|
|
54
|
+
'Max slices to return when the address resolves to a folder (default 20)',
|
|
55
|
+
'20'
|
|
56
|
+
)
|
|
36
57
|
.action(async (name, opts) => {
|
|
37
58
|
const config = requireConfig();
|
|
38
59
|
const wasShortName = !name.startsWith('bitpub://') && !isAliasRef(name);
|
|
@@ -84,7 +105,58 @@ module.exports = function registerLoad(program) {
|
|
|
84
105
|
console.error(` bitpub load <one-of-the-addresses-above>`);
|
|
85
106
|
process.exit(1);
|
|
86
107
|
}
|
|
87
|
-
// Zero candidates: fall through to the not-found block below.
|
|
108
|
+
// Zero candidates: fall through to the folder/not-found block below.
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Folder check: the exact address didn't resolve to a slice, but it
|
|
112
|
+
// may be a *prefix* with children under it (e.g. user typed
|
|
113
|
+
// `bitpub://group:foo/Agents/x` when the real slices live at
|
|
114
|
+
// `.../Agents/x/README`, `.../script`, etc). Auto-load up to
|
|
115
|
+
// --limit children so the caller doesn't have to round-trip.
|
|
116
|
+
// Skip if the user already passed a wildcard — querySlices already
|
|
117
|
+
// handles that path and a 0-result wildcard is a real not-found.
|
|
118
|
+
if (slices.length === 0 && !hcu.includes('*')) {
|
|
119
|
+
const cleanHcu = hcu.replace(/\/$/, '');
|
|
120
|
+
const folderPattern = cleanHcu + '/**';
|
|
121
|
+
// Cap raised slightly above the user-facing default so a folder
|
|
122
|
+
// with N=21 items doesn't get silently truncated below the cap
|
|
123
|
+
// the user asked for; we still slice() to `limit` for display.
|
|
124
|
+
const limit = Math.max(1, parseInt(opts.limit, 10) || 20);
|
|
125
|
+
let descendants = querySlices(folderPattern);
|
|
126
|
+
|
|
127
|
+
if (descendants.length === 0 && opts.fetch !== false) {
|
|
128
|
+
try {
|
|
129
|
+
const api = createApiClient(config);
|
|
130
|
+
const remote = await api.pull(folderPattern, Math.max(limit, 50));
|
|
131
|
+
decryptSlices(remote, config.api_key);
|
|
132
|
+
remote.forEach(upsertSlice);
|
|
133
|
+
descendants = querySlices(folderPattern);
|
|
134
|
+
} catch {
|
|
135
|
+
// Non-fatal — if there's nothing remote either, we'll fall
|
|
136
|
+
// through to the regular not-found message below.
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (descendants.length > 0) {
|
|
141
|
+
const total = descendants.length;
|
|
142
|
+
const truncated = total > limit;
|
|
143
|
+
slices = descendants.slice(0, limit);
|
|
144
|
+
|
|
145
|
+
// Transparency breadcrumb → stderr (keeps stdout pure for pipes).
|
|
146
|
+
// The agent gets a single useful message that tells it (a) what
|
|
147
|
+
// we did and (b) how to escape the cap if 20 wasn't enough.
|
|
148
|
+
if (truncated) {
|
|
149
|
+
console.error(
|
|
150
|
+
`(loaded ${slices.length} of ${total} descendants of "${cleanHcu}" — ` +
|
|
151
|
+
`it's a folder, not a slice; pass --limit ${total} for all of them)`
|
|
152
|
+
);
|
|
153
|
+
} else {
|
|
154
|
+
console.error(
|
|
155
|
+
`(loaded ${slices.length} descendant${slices.length === 1 ? '' : 's'} of ` +
|
|
156
|
+
`"${cleanHcu}" — it's a folder, not a slice)`
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
88
160
|
}
|
|
89
161
|
|
|
90
162
|
if (slices.length === 0) {
|
|
@@ -113,10 +185,15 @@ module.exports = function registerLoad(program) {
|
|
|
113
185
|
return;
|
|
114
186
|
}
|
|
115
187
|
|
|
188
|
+
// Multi-slice raw output (wildcard input or folder auto-load) gets
|
|
189
|
+
// a `=== <address> ===` header per slice so the caller can attribute
|
|
190
|
+
// each chunk. Single-slice loads keep their clean header-free output.
|
|
116
191
|
const parts = slices.map(s => {
|
|
117
192
|
const payload = JSON.parse(s.payload);
|
|
118
|
-
|
|
193
|
+
const content = payload.content ?? '';
|
|
194
|
+
if (slices.length === 1) return content;
|
|
195
|
+
return `=== ${s.hcu} ===\n${content}`;
|
|
119
196
|
});
|
|
120
|
-
console.log(parts.join('\n
|
|
197
|
+
console.log(parts.join(slices.length === 1 ? '' : '\n\n'));
|
|
121
198
|
});
|
|
122
199
|
};
|