prompt-profile 0.2.1 → 0.2.3

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.
Files changed (3) hide show
  1. package/README.md +18 -10
  2. package/dist/bin.js +119 -33
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -6,13 +6,19 @@ collaborate with AI. Uses your logged-in `claude` or `codex` CLI: no API key nee
6
6
  **Analysis sends selected, redacted prompts to your CLI's model service and uses your
7
7
  subscription quota.** Reports and analysis caches stay on your computer.
8
8
 
9
- ![Fictional sample share card](https://erictsai83.github.io/prompt-profile/sample-card.png)
9
+ ![Maintainer collaboration profile](https://erictsai83.github.io/prompt-profile/owner-example/card.en.png)
10
10
 
11
- [Full report](https://erictsai83.github.io/prompt-profile/sample-report.html)
12
- · [Shareable report](https://erictsai83.github.io/prompt-profile/demo/without-personal/report.html)
11
+ [My report](https://erictsai83.github.io/prompt-profile/owner-example/report.html)
12
+ · [繁體中文卡片](https://erictsai83.github.io/prompt-profile/owner-example/card.png)
13
+ · [Fictional example](https://erictsai83.github.io/prompt-profile/sample-report.html)
13
14
  · [Template gallery](https://erictsai83.github.io/prompt-profile/demo/templates/index.html)
14
15
 
15
- All examples use fictional data.
16
+ The card above is my personal analysis from November 1, 2026, rendered with the current
17
+ code. The public report omits prompt quotes, project names and custom instruction names.
18
+ Ability scores describe supported practice in my conversations, not population ranks;
19
+ the API estimate is not my subscription bill. The separate fictional examples and gallery
20
+ are generated from the latest repository code.
21
+ [View the latest README on GitHub](https://github.com/EricTsai83/prompt-profile#readme).
16
22
 
17
23
  ## Quick start
18
24
 
@@ -59,7 +65,9 @@ for extraction and macOS setup.
59
65
  - **PNG share cards**: a 1200 × 630 card per language, without prompt quotes.
60
66
  - **`profile.json`**: the data used to render the report again without another analysis.
61
67
  - **Local history**: successful analyses are kept in a report library; open the output folder's
62
- `index.html` to browse them.
68
+ `index.html` to browse them. Generated output includes a `README.md` explaining each file,
69
+ backups, deletion and index repair; an existing user README is preserved. New report folders use readable local timestamps, for example
70
+ `2026-11-01_03-12-04+0800-<id>` (`+0800` is the UTC offset). Older report folders remain readable.
63
71
 
64
72
  Reports support English and Traditional Chinese; quotes stay in their original language.
65
73
  The default is English, or both languages on a Chinese system locale. Each extra language
@@ -68,9 +76,9 @@ A Chinese-only request stops if translation still fails; it does not substitute
68
76
 
69
77
  Observations cite verified prompt quotes, but quote matching and model review do not prove an
70
78
  interpretation is correct. Small histories may show “Early observations” instead of a role.
71
- The [radar](docs/ability-scores.md) assesses six AI collaboration abilities from verified quotes in these conversations. Scores describe supported practice, not psychological traits or population ranks; “not assessed” is not a low score.
79
+ The [radar](https://github.com/EricTsai83/prompt-profile/blob/main/docs/ability-scores.md) assesses six AI collaboration abilities from verified quotes in these conversations. Scores describe supported practice, not psychological traits or population ranks; “not assessed” is not a low score.
72
80
 
73
- New analyses also retain [reusable instruction provenance](docs/instruction-provenance.md): what a skill is documented to do, who selected it for the task, and which historical requirements were supplied.
81
+ New analyses also retain [reusable instruction provenance](https://github.com/EricTsai83/prompt-profile/blob/main/docs/instruction-provenance.md): what a skill is documented to do, who selected it for the task, and which historical requirements were supplied.
74
82
 
75
83
  ## Usage
76
84
 
@@ -99,7 +107,7 @@ printed retry command: completed chunks and merges are cached; later steps may u
99
107
 
100
108
  Each analysis saves calls, retries, cache reuse, token coverage and cost provenance, including
101
109
  failed executions. The report shows generation usage separately from historical activity.
102
- See [execution accounting](docs/execution-accounting.md) for recovery, unknown measurements
110
+ See [execution accounting](https://github.com/EricTsai83/prompt-profile/blob/main/docs/execution-accounting.md) for recovery, unknown measurements
103
111
  and cost-threshold limits.
104
112
 
105
113
  ## How much a run uses
@@ -207,8 +215,8 @@ source prompts can be in any language.
207
215
 
208
216
  ## Development
209
217
 
210
- See [CONTRIBUTING.md](CONTRIBUTING.md) for setup, checks and releases, and
211
- [AGENTS.md](AGENTS.md) for architecture and repository rules.
218
+ See [CONTRIBUTING.md](https://github.com/EricTsai83/prompt-profile/blob/main/CONTRIBUTING.md) for setup, checks and releases, and
219
+ [AGENTS.md](https://github.com/EricTsai83/prompt-profile/blob/main/AGENTS.md) for architecture and repository rules.
212
220
 
213
221
  ## License
214
222
 
package/dist/bin.js CHANGED
@@ -1493,7 +1493,7 @@ const ChunkObservations = Schema.Struct({
1493
1493
  })).pipe(Schema.withDecodingDefaultKey(Effect.succeed([])))
1494
1494
  });
1495
1495
  // src/domain/Library.ts
1496
- const RUN_ID = /^(?:\d{8}T\d{6}Z-[a-f0-9]{32}|legacy-[a-f0-9]{32})$/u;
1496
+ const RUN_ID = /^(?:(?:\d{8}T\d{6}Z|\d{4}-\d{2}-\d{2}_\d{2}-\d{2}-\d{2}[+-]\d{4})-[a-f0-9]{32}|legacy-[a-f0-9]{32})$/u;
1497
1497
  const Count = Schema.Int.check(Schema.isGreaterThanOrEqualTo(0));
1498
1498
  const Iso = Schema.String.check(Schema.makeFilter((s) => /^\d{4}-\d\d-\d\dT/u.test(s) && Number.isFinite(Date.parse(s))));
1499
1499
  const RunManifest = Schema.Struct({
@@ -2288,6 +2288,83 @@ const readAsset = (name, locate) => new Uint8Array(readFileSync(locate()));
2288
2288
  * `$XDG_CACHE_HOME/prompt-profile`, else `~/.cache/prompt-profile` (macOS too).
2289
2289
  */
2290
2290
  const resolveCacheDir = (env, home, path) => path.join(env["XDG_CACHE_HOME"] || path.join(home, ".cache"), "prompt-profile");
2291
+ // src/platform/output-guide.ts
2292
+ const MARKER = "<!-- Generated by prompt-profile: output guide -->";
2293
+ const GUIDE = `${MARKER}
2294
+ # Your prompt-profile output
2295
+
2296
+ Open **index.html** to browse completed reports, newest first. Inside a report
2297
+ folder, open **report.html** directly. Both work offline without a server.
2298
+ Only files relevant to the commands you ran will be present.
2299
+
2300
+ ## What the files mean
2301
+
2302
+ | File or folder | Purpose |
2303
+ | --- | --- |
2304
+ | index.html | The report library: start here to find the latest report. |
2305
+ | reports/<timestamp>-<id>/ | One completed analysis. Each new analysis gets its own folder. |
2306
+ | reports/legacy-<id>/ | A preserved report from an older output layout. |
2307
+ | report.html | The readable report, with a language switch when available. |
2308
+ | card.png | The summary image in the report's first language. |
2309
+ | card.<language>.png | Additional language images, such as card.en.png. A missing card does not mean the HTML report failed. |
2310
+ | profile.json | The analysis data. Keep this to render supported profiles again without another LLM analysis. Older formats may require the package version that created them. |
2311
+ | run.json | Completion metadata: time, engine, languages, coverage and card status. Keep with the completed report. |
2312
+ | render.json | Rendering choices, version, profile fingerprint and relative source path. |
2313
+ | variants/<name>/ | Alternative renders. Each contains its own report, profile, rendering metadata and available cards. Rendering the same variant again replaces its generated files. |
2314
+ | library.json | Rebuildable catalog of completed reports; it is not the analysis itself. |
2315
+ | executions/<id>/summary.json | Execution status, call accounting and recovery information, including failed or interrupted runs. These are separate from completed reports. |
2316
+ | stats.json | Statistics-only output from the stats command; replaced the next time stats uses this folder. |
2317
+ | plan.json and chunks/ | Dry-run estimate and redacted text prepared for analysis. Use a separate output folder for each dry run; later runs may leave older chunks. |
2318
+ | README.md | This generated guide. Your own existing README is left untouched. |
2319
+
2320
+ ## Dates and names
2321
+
2322
+ New analysis folders use the computer's local time, for example
2323
+ 2026-11-01_03-12-04+0800-<id>: November 1, 2026 at 03:12:04, UTC+08:00.
2324
+ The final ID prevents collisions. Older names such as 20261031T191204Z-<id>
2325
+ use UTC (Z); they represent the same instant in this example. JSON timestamps
2326
+ remain in machine-readable formats. The library orders reports by completion
2327
+ time, so use index.html rather than sorting mixed old and new folder names.
2328
+
2329
+ ## Keep, move or delete
2330
+
2331
+ - Back up or move the whole output folder to preserve relative links. Copy a
2332
+ complete report folder when archiving one analysis; its link back to the library
2333
+ needs the original surrounding layout. Do not rename report folders or edit IDs.
2334
+ - To remove an analysis, delete its whole folder under reports/, including any
2335
+ variants you no longer need. This permanently removes that saved analysis.
2336
+ Never delete profile.json or run.json alone from a report you want to keep.
2337
+ - After deleting reports, rebuild index.html and library.json without an LLM call:
2338
+
2339
+ prompt-profile history --out "<output-folder>"
2340
+
2341
+ - Unwanted variants, stats.json, dry-run plan.json and chunks/ can be deleted
2342
+ independently. Keep profile.json if you want to render a variant again.
2343
+ - Execution folders can be deleted independently when their diagnostics are no
2344
+ longer needed. Deleting them does not remove completed reports or the separate
2345
+ analysis cache; keep them while troubleshooting an interrupted run.
2346
+ - Hidden reports/.pending-* folders are unfinished output. Remove them only after
2347
+ confirming that no prompt-profile process is using this output folder.
2348
+ - Older root-level report files and variants may have been copied to a legacy
2349
+ archive. Remove originals only after verifying that the archived copies match.
2350
+
2351
+ These files contain personal analysis and may include prompt excerpts, local paths
2352
+ or redacted input. Review the files you choose to share. Render and history commands
2353
+ work locally; neither requires another LLM analysis. No saved report history is
2354
+ pruned automatically.
2355
+ `;
2356
+ /** Refresh our generated guide without replacing user notes or following README symlinks. */
2357
+ const writeOutputGuide = (root) => Effect.gen(function* () {
2358
+ const fs = yield* FileSystem.FileSystem;
2359
+ const path = yield* Path.Path;
2360
+ const file = path.join(root, "README.md");
2361
+ const info = yield* fs.lstat(file).pipe(Effect.catchIf(isNotFound$1, () => Effect.succeed(null)));
2362
+ if (info !== null) {
2363
+ if (info.type !== "File" || info.size > 64000n) return;
2364
+ if (!(yield* fs.readFileString(file)).startsWith(`${MARKER}\n`)) return;
2365
+ }
2366
+ yield* writePrivateFile(fs, path, file, GUIDE);
2367
+ }).pipe(Effect.mapError(toFileError(root)));
2291
2368
  // src/platform/executions.ts
2292
2369
  const ID = /^[0-9a-f-]{36}$/u;
2293
2370
  /** `<root>/executions`, never a symlink. */
@@ -2331,6 +2408,7 @@ const createExecution = (root, startedAtMs) => Effect.gen(function* () {
2331
2408
  const fs = yield* FileSystem.FileSystem;
2332
2409
  const path = yield* Path.Path;
2333
2410
  const folder = yield* directory(root, true);
2411
+ yield* writeOutputGuide(root);
2334
2412
  const id = randomUUID();
2335
2413
  yield* fs.makeDirectory(path.join(folder, id), { mode: 448 });
2336
2414
  const initial = {
@@ -2422,7 +2500,12 @@ const reportsDirectory = (root, create) => Effect.gen(function* () {
2422
2500
  });
2423
2501
  const createStage = (root) => Effect.gen(function* () {
2424
2502
  const reports = yield* reportsDirectory(root, true);
2425
- const id = `${(/* @__PURE__ */ new Date()).toISOString().replace(/[-:]/gu, "").replace(/\.\d{3}Z$/u, "Z")}-${randomUUID().replaceAll("-", "")}`;
2503
+ const now = /* @__PURE__ */ new Date();
2504
+ const pad = (value) => String(value).padStart(2, "0");
2505
+ const date = `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}`;
2506
+ const time = `${pad(now.getHours())}-${pad(now.getMinutes())}-${pad(now.getSeconds())}`;
2507
+ const offset = -now.getTimezoneOffset();
2508
+ const id = `${`${date}_${time}${`${offset >= 0 ? "+" : "-"}${pad(Math.floor(Math.abs(offset) / 60))}${pad(Math.abs(offset) % 60)}`}`}-${randomUUID().replaceAll("-", "")}`;
2426
2509
  const directory = (yield* Path.Path).join(reports, `.pending-${id}`);
2427
2510
  yield* (yield* FileSystem.FileSystem).makeDirectory(directory, { mode: 448 });
2428
2511
  return {
@@ -2461,6 +2544,7 @@ const scan = (root) => Effect.gen(function* () {
2461
2544
  const repair = (root, render) => Effect.gen(function* () {
2462
2545
  const path = yield* Path.Path;
2463
2546
  yield* reportsDirectory(root, true);
2547
+ yield* writeOutputGuide(root);
2464
2548
  for (let attempt = 0; attempt < 32; attempt++) {
2465
2549
  const index = yield* scan(root);
2466
2550
  yield* write(path.join(root, "library.json"), json(index));
@@ -4402,7 +4486,7 @@ const productDark = (bg) => ({
4402
4486
  });
4403
4487
  const CLASSIC = {
4404
4488
  id: "classic",
4405
- version: 36,
4489
+ version: 37,
4406
4490
  color: {
4407
4491
  light: {
4408
4492
  bg: "#f6f6f3",
@@ -5079,7 +5163,7 @@ const fontsOf = (theme) => {
5079
5163
  /** Color tokens of a classic or product card. */
5080
5164
  const schemeOf = (theme) => theme.color[theme.card.scheme];
5081
5165
  /** The archetype name, headline and trait chips of a classic or identity card. */
5082
- const identityCopy = (profile, theme, hide, lang, showGraphic, colors, badge = null, width) => {
5166
+ const identityCopy = (profile, theme, hide, lang, showGraphic, colors, badge = null, width, period = null) => {
5083
5167
  const c = contentIn(profile, lang);
5084
5168
  const card = theme.card;
5085
5169
  const font = fontsOf(theme);
@@ -5112,6 +5196,12 @@ const identityCopy = (profile, theme, hide, lang, showGraphic, colors, badge = n
5112
5196
  marginTop: card.headline.gap,
5113
5197
  color: colors.muted
5114
5198
  }, clip(c.headline, 120)),
5199
+ period === null ? null : h("div", {
5200
+ fontSize: card.size.header,
5201
+ color: colors.muted,
5202
+ marginTop: card.headline.gap,
5203
+ flexShrink: 0
5204
+ }, period),
5115
5205
  hide.includes("traits") ? null : h("div", {
5116
5206
  display: "flex",
5117
5207
  gap: card.chips.gap,
@@ -5217,7 +5307,6 @@ const CLASSIC_RADAR = {
5217
5307
  */
5218
5308
  const classicTree = (profile, theme, hide, lang, showUsage, exactDates, avatar) => {
5219
5309
  const k = schemeOf(theme);
5220
- const card = theme.card;
5221
5310
  const showGraphic = !hide.includes("graphic");
5222
5311
  return h("div", {
5223
5312
  width: CARD_WIDTH,
@@ -5234,31 +5323,24 @@ const classicTree = (profile, theme, hide, lang, showUsage, exactDates, avatar)
5234
5323
  muted: k.muted,
5235
5324
  value: k.accent,
5236
5325
  card: k.card
5237
- }, !hide.includes("traits")) : [], copyLayer(theme, [
5238
- h("div", {
5239
- display: "flex",
5240
- justifyContent: "flex-end",
5241
- alignItems: "center"
5242
- }, [hide.includes("stats") ? null : h("div", {
5243
- fontSize: card.size.header,
5244
- color: k.muted
5245
- }, cardPeriod(profile, lang, exactDates))]),
5246
- identityCopy(profile, theme, showGraphic ? [...hide, "traits"] : hide, lang, showGraphic, {
5247
- muted: k.muted,
5248
- chip: {
5249
- color: k.accent,
5250
- backgroundColor: k.accentSoft
5251
- }
5252
- }, avatar && showGraphic ? roleBadge(avatar, lang, {
5253
- fg: k.fg,
5254
- border: k.border
5255
- }) : null, showGraphic ? 520 : void 0),
5256
- h("div", {
5257
- display: "flex",
5258
- flexDirection: "column",
5259
- gap: 18
5260
- }, [metricRow(profile, theme, hide, lang, k.muted, showUsage), cardFooter(profile, theme, hide, lang, k.muted, showUsage, exactDates)])
5261
- ])]);
5326
+ }, !hide.includes("traits")) : [], copyLayer(theme, [h("div", {
5327
+ display: "flex",
5328
+ flex: 1,
5329
+ alignItems: "center"
5330
+ }, [identityCopy(profile, theme, showGraphic ? [...hide, "traits"] : hide, lang, showGraphic, {
5331
+ muted: k.muted,
5332
+ chip: {
5333
+ color: k.accent,
5334
+ backgroundColor: k.accentSoft
5335
+ }
5336
+ }, avatar && showGraphic ? roleBadge(avatar, lang, {
5337
+ fg: k.fg,
5338
+ border: k.border
5339
+ }) : null, showGraphic ? 520 : void 0, hide.includes("stats") ? null : cardPeriod(profile, lang, exactDates))]), h("div", {
5340
+ display: "flex",
5341
+ flexDirection: "column",
5342
+ gap: 18
5343
+ }, [metricRow(profile, theme, hide, lang, k.muted, showUsage), cardFooter(profile, theme, hide, lang, k.muted, showUsage, exactDates)])])]);
5262
5344
  };
5263
5345
  /** A share-first look: identity colors, display type and the activity graphic. */
5264
5346
  const identityTree = (profile, theme, hide, lang, showUsage, exactDates, avatar) => {
@@ -7999,7 +8081,7 @@ const cardFileName = (lang, isDefault) => isDefault ? "card.png" : `card.${lang}
7999
8081
  const noCjkFonts = Effect.fail(new FontUnavailable({ reason: "no CJK font available" }));
8000
8082
  /**
8001
8083
  * Write `report.html`, `profile.json` and one card per language into `outDir` (overwriting
8002
- * only those files). A card failure never fails the report. Every render also writes
8084
+ * only generated files, including README.md). A card failure never fails the report. Every render also writes
8003
8085
  * `render.json`: its options and the profile it came from (`source`, relative to `outDir`).
8004
8086
  */
8005
8087
  const writeReport = (profile, outDir, options = DEFAULT_RENDER_OPTIONS, source = null, cjkFonts = noCjkFonts) => Effect.gen(function* () {
@@ -8057,6 +8139,7 @@ const writeReport = (profile, outDir, options = DEFAULT_RENDER_OPTIONS, source =
8057
8139
  error: "reason" in e ? e.reason : e.message
8058
8140
  }))));
8059
8141
  }
8142
+ yield* writeOutputGuide(outDir).pipe(Effect.mapError((e) => new Error(e.detail)));
8060
8143
  const first = cards[0];
8061
8144
  return {
8062
8145
  html,
@@ -14337,7 +14420,7 @@ const analyze = (input) => Effect.gen(function* () {
14337
14420
  * Package version. Injected by tsdown at build time; during development and tests
14338
14421
  * (tsx / vitest) it falls back to reading package.json.
14339
14422
  */
14340
- const VERSION = "0.2.1";
14423
+ const VERSION = "0.2.3";
14341
14424
  // src/cli/render-error.ts
14342
14425
  /** How to fix an InvalidArgument, per flag. */
14343
14426
  const ARGUMENT_HINTS = {
@@ -14578,6 +14661,7 @@ const writeDryRun = (p, outDir, context, voice = DEFAULT_VOICE) => Effect.gen(fu
14578
14661
  }))
14579
14662
  };
14580
14663
  yield* writeFileOrFail(path.join(outDir, "plan.json"), `${JSON.stringify(summary, null, 2)}\n`);
14664
+ yield* writeOutputGuide(outDir);
14581
14665
  });
14582
14666
  /**
14583
14667
  * The plan a CLI reports wins over the hint from local files; the file's extra detail is kept.
@@ -14954,8 +15038,10 @@ const runStats = (options) => Effect.gen(function* () {
14954
15038
  yield* out.line("");
14955
15039
  const collected = yield* readSources;
14956
15040
  const { stats } = yield* runPipelineStep(collected, options.since);
14957
- const file = path.join(yield* resolveOut(options.out), "stats.json");
15041
+ const root = yield* resolveOut(options.out);
15042
+ const file = path.join(root, "stats.json");
14958
15043
  yield* writeFileOrFail(file, `${JSON.stringify(encodeStats(stats), null, 2)}\n`);
15044
+ yield* writeOutputGuide(root);
14959
15045
  const peak = peakHour(stats);
14960
15046
  const percent = (x) => Math.round(x * 100);
14961
15047
  yield* out.line("");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prompt-profile",
3
- "version": "0.2.1",
3
+ "version": "0.2.3",
4
4
  "description": "Read your Claude Code / Codex prompt history locally, analyse it with your logged-in claude or codex CLI, and generate a personal profile report.",
5
5
  "keywords": [
6
6
  "ai",