@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.
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blockrun/franklin",
3
- "version": "3.15.57",
3
+ "version": "3.15.58",
4
4
  "description": "Franklin — The AI agent with a wallet. Spends USDC autonomously to get real work done. Pay per action, no subscriptions.",
5
5
  "type": "module",
6
6
  "exports": {