@blockrun/franklin 3.15.57 → 3.15.58
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/dist/commands/content.d.ts +17 -0
- package/dist/commands/content.js +141 -0
- package/dist/index.js +5 -0
- package/package.json +1 -1
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `franklin content` CLI surface — human-facing read access to the
|
|
3
|
+
* Content library that lives at ~/.blockrun/content.json.
|
|
4
|
+
*
|
|
5
|
+
* Tools (ContentCreate / ContentAddAsset) write the library during agent
|
|
6
|
+
* sessions; before this command, there was no way to see the resulting
|
|
7
|
+
* spend without scripting against the JSON file. Verified 2026-05-04 in
|
|
8
|
+
* a live session: user asked "我花了多少钱做这个", agent ran
|
|
9
|
+
* `franklin content list` and got "no content subcommand", fell back to
|
|
10
|
+
* estimating from memory.
|
|
11
|
+
*
|
|
12
|
+
* Subcommands:
|
|
13
|
+
* - list : table of id, type, title, status, spent/budget, assets
|
|
14
|
+
* - show <idOrPrefix>: full detail of one Content, including each asset
|
|
15
|
+
*/
|
|
16
|
+
import { Command } from 'commander';
|
|
17
|
+
export declare function buildContentCommand(): Command;
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `franklin content` CLI surface — human-facing read access to the
|
|
3
|
+
* Content library that lives at ~/.blockrun/content.json.
|
|
4
|
+
*
|
|
5
|
+
* Tools (ContentCreate / ContentAddAsset) write the library during agent
|
|
6
|
+
* sessions; before this command, there was no way to see the resulting
|
|
7
|
+
* spend without scripting against the JSON file. Verified 2026-05-04 in
|
|
8
|
+
* a live session: user asked "我花了多少钱做这个", agent ran
|
|
9
|
+
* `franklin content list` and got "no content subcommand", fell back to
|
|
10
|
+
* estimating from memory.
|
|
11
|
+
*
|
|
12
|
+
* Subcommands:
|
|
13
|
+
* - list : table of id, type, title, status, spent/budget, assets
|
|
14
|
+
* - show <idOrPrefix>: full detail of one Content, including each asset
|
|
15
|
+
*/
|
|
16
|
+
import os from 'node:os';
|
|
17
|
+
import path from 'node:path';
|
|
18
|
+
import { Command } from 'commander';
|
|
19
|
+
import { loadLibrary } from '../content/store.js';
|
|
20
|
+
const DEFAULT_PATH = path.join(os.homedir(), '.blockrun', 'content.json');
|
|
21
|
+
function fmtUsd(n) {
|
|
22
|
+
return `$${n.toFixed(2)}`;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Resolve a user-typed id-or-prefix to a single Content record. Returns
|
|
26
|
+
* either the matching record or an error message; the caller prints.
|
|
27
|
+
*
|
|
28
|
+
* Accepts the full UUID, a prefix (≥4 chars), or — for convenience — a
|
|
29
|
+
* substring match against the title (case-insensitive). Ambiguity returns
|
|
30
|
+
* the candidates so the user can disambiguate without rerunning blind.
|
|
31
|
+
*/
|
|
32
|
+
function resolveContent(all, input) {
|
|
33
|
+
const q = input.trim();
|
|
34
|
+
if (!q)
|
|
35
|
+
return { error: 'Provide an id, id-prefix, or title substring.' };
|
|
36
|
+
const exact = all.find(c => c.id === q);
|
|
37
|
+
if (exact)
|
|
38
|
+
return { found: exact };
|
|
39
|
+
const prefix = q.length >= 4 ? all.filter(c => c.id.startsWith(q)) : [];
|
|
40
|
+
if (prefix.length === 1)
|
|
41
|
+
return { found: prefix[0] };
|
|
42
|
+
if (prefix.length > 1) {
|
|
43
|
+
return { error: `Ambiguous prefix "${q}" — matches:\n${prefix.map(c => ` ${c.id} ${c.title}`).join('\n')}` };
|
|
44
|
+
}
|
|
45
|
+
const lower = q.toLowerCase();
|
|
46
|
+
const titled = all.filter(c => c.title.toLowerCase().includes(lower));
|
|
47
|
+
if (titled.length === 1)
|
|
48
|
+
return { found: titled[0] };
|
|
49
|
+
if (titled.length > 1) {
|
|
50
|
+
return { error: `Ambiguous title "${q}" — matches:\n${titled.map(c => ` ${c.id} ${c.title}`).join('\n')}` };
|
|
51
|
+
}
|
|
52
|
+
return { error: `No Content matches "${q}".` };
|
|
53
|
+
}
|
|
54
|
+
export function buildContentCommand() {
|
|
55
|
+
const cmd = new Command('content').description('Inspect Content library (assets, spend, budget)');
|
|
56
|
+
cmd
|
|
57
|
+
.command('list')
|
|
58
|
+
.description('List all Content records, newest first')
|
|
59
|
+
.action(() => {
|
|
60
|
+
const lib = loadLibrary(DEFAULT_PATH);
|
|
61
|
+
if (!lib) {
|
|
62
|
+
console.log('No Content library yet. Tools like ContentCreate populate it during agent sessions.');
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
const all = lib.list();
|
|
66
|
+
if (all.length === 0) {
|
|
67
|
+
console.log('No Content records.');
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
// Header + rows. Truncate id to 8-char prefix and title to 40 chars
|
|
71
|
+
// so common terminal widths (80-100) fit a row on one line.
|
|
72
|
+
console.log(['id'.padEnd(8), 'type'.padEnd(8), 'status'.padEnd(10), 'spent/cap'.padEnd(13), 'assets', 'title'].join(' '));
|
|
73
|
+
for (const c of all) {
|
|
74
|
+
const id8 = c.id.slice(0, 8);
|
|
75
|
+
const spend = `${fmtUsd(c.spentUsd)}/${fmtUsd(c.budgetUsd)}`;
|
|
76
|
+
const title = c.title.length > 40 ? c.title.slice(0, 39) + '…' : c.title;
|
|
77
|
+
console.log([
|
|
78
|
+
id8.padEnd(8),
|
|
79
|
+
c.type.padEnd(8),
|
|
80
|
+
c.status.padEnd(10),
|
|
81
|
+
spend.padEnd(13),
|
|
82
|
+
String(c.assets.length).padEnd(6),
|
|
83
|
+
title,
|
|
84
|
+
].join(' '));
|
|
85
|
+
}
|
|
86
|
+
// Footer with rolled-up spend.
|
|
87
|
+
const totalSpent = all.reduce((s, c) => s + c.spentUsd, 0);
|
|
88
|
+
const totalBudget = all.reduce((s, c) => s + c.budgetUsd, 0);
|
|
89
|
+
console.log();
|
|
90
|
+
console.log(`Total: ${fmtUsd(totalSpent)} spent across ${all.length} content${all.length === 1 ? '' : 's'} (cap ${fmtUsd(totalBudget)}).`);
|
|
91
|
+
});
|
|
92
|
+
cmd
|
|
93
|
+
.command('show <idOrPrefix>')
|
|
94
|
+
.description('Show full detail for one Content record (id, prefix, or title substring)')
|
|
95
|
+
.action((input) => {
|
|
96
|
+
const lib = loadLibrary(DEFAULT_PATH);
|
|
97
|
+
if (!lib) {
|
|
98
|
+
console.log('No Content library yet.');
|
|
99
|
+
process.exit(1);
|
|
100
|
+
}
|
|
101
|
+
const result = resolveContent(lib.list(), input);
|
|
102
|
+
if ('error' in result) {
|
|
103
|
+
console.error(result.error);
|
|
104
|
+
process.exit(1);
|
|
105
|
+
}
|
|
106
|
+
const c = result.found;
|
|
107
|
+
console.log(`# ${c.title}`);
|
|
108
|
+
console.log();
|
|
109
|
+
console.log(`id: ${c.id}`);
|
|
110
|
+
console.log(`type: ${c.type}`);
|
|
111
|
+
console.log(`status: ${c.status}`);
|
|
112
|
+
console.log(`spent: ${fmtUsd(c.spentUsd)} / ${fmtUsd(c.budgetUsd)} cap`);
|
|
113
|
+
console.log(`created: ${new Date(c.createdAt).toISOString()}`);
|
|
114
|
+
if (c.publishedAt)
|
|
115
|
+
console.log(`published: ${new Date(c.publishedAt).toISOString()}`);
|
|
116
|
+
console.log();
|
|
117
|
+
if (c.assets.length > 0) {
|
|
118
|
+
console.log(`## Assets (${c.assets.length})`);
|
|
119
|
+
for (const a of c.assets) {
|
|
120
|
+
console.log(`- ${a.kind.padEnd(6)} ${fmtUsd(a.costUsd).padStart(7)} ${a.source}`);
|
|
121
|
+
console.log(` ${a.data}`);
|
|
122
|
+
}
|
|
123
|
+
console.log();
|
|
124
|
+
}
|
|
125
|
+
if (c.drafts.length > 0) {
|
|
126
|
+
console.log(`## Drafts (${c.drafts.length})`);
|
|
127
|
+
c.drafts.forEach((d, i) => {
|
|
128
|
+
const preview = d.text.length > 80 ? d.text.slice(0, 79) + '…' : d.text;
|
|
129
|
+
console.log(`- #${i + 1} ${preview}`);
|
|
130
|
+
});
|
|
131
|
+
console.log();
|
|
132
|
+
}
|
|
133
|
+
if (c.distribution.length > 0) {
|
|
134
|
+
console.log(`## Distribution (${c.distribution.length})`);
|
|
135
|
+
for (const dist of c.distribution) {
|
|
136
|
+
console.log(`- ${dist.channel}${dist.url ? ` ${dist.url}` : ''} (${new Date(dist.at).toISOString()})`);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
return cmd;
|
|
141
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -24,6 +24,7 @@ import { initCommand } from './commands/init.js';
|
|
|
24
24
|
import { uninitCommand } from './commands/uninit.js';
|
|
25
25
|
import { proxyCommand } from './commands/proxy.js';
|
|
26
26
|
import { buildTaskCommand } from './commands/task.js';
|
|
27
|
+
import { buildContentCommand } from './commands/content.js';
|
|
27
28
|
import { VERSION as version } from './config.js';
|
|
28
29
|
const program = new Command();
|
|
29
30
|
program
|
|
@@ -221,6 +222,10 @@ program
|
|
|
221
222
|
// `franklin task <subcmd>` — human-facing CLI for detached background tasks.
|
|
222
223
|
// Defined in src/commands/task.ts; subcommands: list, tail, cancel, wait.
|
|
223
224
|
program.addCommand(buildTaskCommand());
|
|
225
|
+
// `franklin content <subcmd>` — read access to the Content library
|
|
226
|
+
// (~/.blockrun/content.json) so users + agent shell-outs can inspect
|
|
227
|
+
// spend without scripting against the JSON file. Subcommands: list, show.
|
|
228
|
+
program.addCommand(buildContentCommand());
|
|
224
229
|
// Hidden internal subcommand — invoked by startDetachedTask via spawn(detached).
|
|
225
230
|
// The underscore prefix signals "not for humans"; we still register it via
|
|
226
231
|
// commander so exit codes and arg parsing stay consistent with the rest of
|
package/package.json
CHANGED