@chenpengfei/daily-brief 0.1.1 → 0.1.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.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,49 @@
2
2
 
3
3
  All notable changes for Formal Releases are recorded here. GitHub Release notes should be derived from the matching version entry.
4
4
 
5
+ ## 0.1.3 - 2026-06-06
6
+
7
+ Patch release for runtime configuration hardening and richer local status output.
8
+
9
+ ### User-visible Changes
10
+
11
+ - Expands `daily-brief status` into a local readiness report with setup checks, today's run state, active paths, system timezone, and the next suggested action.
12
+ - Keeps model and Discord secrets in `auth.json` referenced by `config.yaml`, removing runtime reliance on `env:NAME`, `.env`, and `DISCORD_WEBHOOK_URL` credential paths for installed usage.
13
+ - Reports Discord delivery as explicitly skipped when delivery is disabled or the stored webhook credential is missing.
14
+ - Keeps `daily-brief run-once --date YYYY-MM-DD` available for manual backfills while making `daily-brief status` a no-flag inspection command.
15
+
16
+ ### Installation and Upgrade Notes
17
+
18
+ - Upgrade with `npm install -g @chenpengfei/daily-brief@latest`.
19
+ - Run `daily-brief setup` after upgrading if your previous model or Discord configuration used environment-backed credential references.
20
+ - Scripted environments should write `config.yaml`, `sources.yaml`, and `auth.json` directly under `DAILY_BRIEF_HOME`; use `DAILY_BRIEF_DATA_HOME` when generated data should live elsewhere.
21
+
22
+ ### Known Limitations
23
+
24
+ - `daily-brief status` is read-only and local: it does not collect Sources, call an LLM, refresh credentials, generate a brief, or send Discord notifications.
25
+ - Environment variables remain limited to path overrides for installed usage: `DAILY_BRIEF_HOME` and `DAILY_BRIEF_DATA_HOME`.
26
+
27
+ ## 0.1.2 - 2026-06-04
28
+
29
+ Patch release for the simplified public CLI setup workflow.
30
+
31
+ ### User-visible Changes
32
+
33
+ - Simplifies the public CLI around `setup`, `run-once`, `status`, `sources`, `version`, and help.
34
+ - Reworks `daily-brief setup` into an interactive wizard that preserves existing files by default and guides model credential and optional Discord delivery configuration.
35
+ - Adds human-readable `run-once` progress output and clearer Source listing/edit guidance.
36
+ - Makes `delivery.enabled: false` take precedence over `DISCORD_WEBHOOK_URL`, preventing accidental Discord sends when delivery is disabled in config.
37
+
38
+ ### Installation and Upgrade Notes
39
+
40
+ - Upgrade with `npm install -g @chenpengfei/daily-brief@latest`.
41
+ - Run `daily-brief setup` again after upgrading to review the simplified setup flow.
42
+ - Non-interactive setup now fails before writing configuration files; scripted environments should write `config.yaml`, `sources.yaml`, and `auth.json` directly.
43
+
44
+ ### Known Limitations
45
+
46
+ - The simplified public CLI removes direct public `collect`, `generate`, `deliver`, `model`, and `delivery` command entry points. Use `setup`, `run-once`, `status`, and `sources` for normal installed usage.
47
+
5
48
  ## 0.1.1 - 2026-06-01
6
49
 
7
50
  Patch release for first-install command discovery.
package/README.md CHANGED
@@ -25,8 +25,7 @@ Daily Brief requires Node.js 22 or newer.
25
25
  daily-brief run-once
26
26
  daily-brief status
27
27
  daily-brief sources list
28
- daily-brief model status
29
- daily-brief delivery status
28
+ daily-brief version
30
29
  ```
31
30
 
32
31
  For installation, setup, configuration, upgrade, and troubleshooting, see `docs/user-manual.md`.
@@ -17,6 +17,7 @@ export async function runOnce(options = {}) {
17
17
  const dateKey = options.dateKey;
18
18
  let collection;
19
19
  try {
20
+ options.onProgress?.("1/5 Collecting Source Items");
20
21
  collection = await collectSources({
21
22
  date,
22
23
  ...(dateKey ? { dateKey } : {}),
@@ -24,6 +25,9 @@ export async function runOnce(options = {}) {
24
25
  ...(options.sourceRegistryPath ? { sourceRegistryPath: options.sourceRegistryPath } : {}),
25
26
  ...(options.sourceItemRoot ? { sourceItemRoot: options.sourceItemRoot } : {})
26
27
  });
28
+ for (const source of collection.sources) {
29
+ options.onProgress?.(`- ${source.sourceId}: ${source.status}, items ${source.itemCount}, written ${source.writtenCount}, duplicates ${source.skippedDuplicateCount}${source.reason ? `, ${source.reason}` : ""}`);
30
+ }
27
31
  }
28
32
  catch (error) {
29
33
  const coreFailure = {
@@ -32,6 +36,7 @@ export async function runOnce(options = {}) {
32
36
  };
33
37
  const delivery = await deliverCoreFailureNotification(coreFailure, {
34
38
  ...(options.discordWebhookUrl ? { webhookUrl: options.discordWebhookUrl } : {}),
39
+ ...(options.discordEnv ? { env: options.discordEnv } : {}),
35
40
  ...(options.discordFetchImpl ? { fetchImpl: options.discordFetchImpl } : {})
36
41
  });
37
42
  return {
@@ -50,6 +55,7 @@ export async function runOnce(options = {}) {
50
55
  if (collectionFailure) {
51
56
  const delivery = await deliverCoreFailureNotification(collectionFailure, {
52
57
  ...(options.discordWebhookUrl ? { webhookUrl: options.discordWebhookUrl } : {}),
58
+ ...(options.discordEnv ? { env: options.discordEnv } : {}),
53
59
  ...(options.discordFetchImpl ? { fetchImpl: options.discordFetchImpl } : {})
54
60
  });
55
61
  return {
@@ -64,6 +70,7 @@ export async function runOnce(options = {}) {
64
70
  };
65
71
  }
66
72
  const collectionFailures = collectionFailureRefs(collection);
73
+ options.onProgress?.("2/5 Generating Daily Brief");
67
74
  const generated = await generateOnce({
68
75
  ...options,
69
76
  ...(dateKey ? { dateKey } : {}),
@@ -75,6 +82,7 @@ export async function runOnce(options = {}) {
75
82
  }
76
83
  : {})
77
84
  });
85
+ options.onProgress?.("4/5 Delivering Notification");
78
86
  const delivery = await deliverOnce({
79
87
  ...options,
80
88
  date,
@@ -124,6 +132,7 @@ export async function generateOnce(options = {}) {
124
132
  const collectionInputRefs = options.collectionFailures
125
133
  ? { collectionFailures: options.collectionFailures }
126
134
  : undefined;
135
+ options.onProgress?.("- Understanding Source Items");
127
136
  const understanding = await runSourceItemUnderstandingStage({
128
137
  sourceItems,
129
138
  modelRuntimeConfig,
@@ -131,6 +140,7 @@ export async function generateOnce(options = {}) {
131
140
  ...(collectionInputRefs ? { inputRefs: collectionInputRefs } : {}),
132
141
  ...(options.modelRuntimeEnv ? { modelRuntimeEnv: options.modelRuntimeEnv } : {})
133
142
  });
143
+ options.onProgress?.("- Selecting and Ranking Signals");
134
144
  const selected = await runSignalSelectionAndRankingStages({
135
145
  sourceItems,
136
146
  annotations: understanding.annotations,
@@ -147,6 +157,7 @@ export async function generateOnce(options = {}) {
147
157
  signals: selected.signals
148
158
  };
149
159
  const narrativeEvents = [];
160
+ options.onProgress?.("- Writing Narrative");
150
161
  const narrative = await enrichDailyBriefNarrativeWithAgent({
151
162
  brief: selectedBrief,
152
163
  sourceItems,
@@ -161,6 +172,7 @@ export async function generateOnce(options = {}) {
161
172
  brief: narrative.brief,
162
173
  ...(options.collectionFailures ? { collectionFailures: options.collectionFailures } : {})
163
174
  });
175
+ options.onProgress?.("- Checking Source Grounding");
164
176
  const audited = await auditBriefWithSingleRepairAttempt({
165
177
  narrativeBrief: narrative.brief,
166
178
  sourceItems,
@@ -175,6 +187,7 @@ export async function generateOnce(options = {}) {
175
187
  const writtenArtifact = options.agentRunRoot ? await writeAgentRunArtifact(artifact, date, options.agentRunRoot, dateKey) : undefined;
176
188
  const markdown = renderDailyBriefMarkdown(audited.brief);
177
189
  const piResult = await renderBriefThroughPiRuntime(markdown);
190
+ options.onProgress?.("3/5 Archiving Daily Brief");
178
191
  const archived = await writeBriefArchive(piResult.markdown, date, options.archiveRoot, dateKey);
179
192
  return {
180
193
  archivePath: archived.path,
@@ -309,6 +322,7 @@ export async function deliverOnce(options = {}) {
309
322
  ...(options.discordTemplatePath ? { templatePath: options.discordTemplatePath } : {})
310
323
  }, {
311
324
  ...(options.discordWebhookUrl ? { webhookUrl: options.discordWebhookUrl } : {}),
325
+ ...(options.discordEnv ? { env: options.discordEnv } : {}),
312
326
  ...(options.discordFetchImpl ? { fetchImpl: options.discordFetchImpl } : {})
313
327
  });
314
328
  }
@@ -1,6 +1,6 @@
1
1
  import { existsSync } from "node:fs";
2
2
  import { getOAuthApiKey, loginOpenAICodex } from "@earendil-works/pi-ai/oauth";
3
- import { envNameFromCredentialRef, getCredential, isEnvCredentialRef, putCredential, readCredentialStore, readModelConfig, removeCredential, writeCredentialStore } from "../config/index.js";
3
+ import { getCredential, putCredential, readCredentialStore, readModelConfig, removeCredential, writeCredentialStore } from "../config/index.js";
4
4
  import { resolveDailyBriefPaths } from "../config/paths.js";
5
5
  export const defaultOAuthHelpers = {
6
6
  getOAuthApiKey,
@@ -10,17 +10,17 @@ export function readModelRuntimeConfig(env = process.env, options = {}) {
10
10
  const paths = resolveDailyBriefPaths(env);
11
11
  const configPath = options.configPath ?? paths.configPath;
12
12
  const authPath = options.authPath ?? paths.authPath;
13
- const config = readEnvModelConfig(env) ?? (existsSync(configPath) ? readModelConfig(configPath) : undefined);
13
+ const config = existsSync(configPath) ? readModelConfig(configPath) : undefined;
14
14
  if (!config) {
15
15
  return {
16
16
  provider: "openai-codex",
17
17
  model: "gpt-5.5",
18
18
  credentialRef: "openai-codex.default",
19
19
  ready: false,
20
- issues: [`Model config not found: ${configPath}. Run daily-brief setup and daily-brief model configure/login first.`]
20
+ issues: [`Model config not found: ${configPath}. Run daily-brief setup or create config.yaml with model settings.`]
21
21
  };
22
22
  }
23
- const issues = credentialIssues(config.provider, config.credentialRef, env, authPath);
23
+ const issues = credentialIssues(config.provider, config.credentialRef, authPath);
24
24
  return {
25
25
  provider: config.provider,
26
26
  model: config.model,
@@ -38,10 +38,6 @@ export async function resolveModelApiKey(config, env = process.env, options = {}
38
38
  if (!credentialRef) {
39
39
  throw new Error(`credentialRef is required for ${config.provider}`);
40
40
  }
41
- if (isEnvCredentialRef(credentialRef)) {
42
- const envName = envNameFromCredentialRef(credentialRef);
43
- return env[envName]?.trim();
44
- }
45
41
  const authPath = options.authPath ?? resolveDailyBriefPaths(env).authPath;
46
42
  const credential = getCredential(credentialRef, authPath);
47
43
  if (!credential) {
@@ -108,17 +104,13 @@ export function statusModelCredentials(env = process.env, options = {}) {
108
104
  secret: "<redacted>"
109
105
  }));
110
106
  }
111
- function credentialIssues(provider, credentialRef, env, authPath) {
107
+ function credentialIssues(provider, credentialRef, authPath) {
112
108
  if (provider === "faux") {
113
109
  return [];
114
110
  }
115
111
  if (!credentialRef) {
116
112
  return [`credentialRef is required for ${provider}`];
117
113
  }
118
- if (isEnvCredentialRef(credentialRef)) {
119
- const envName = envNameFromCredentialRef(credentialRef);
120
- return env[envName]?.trim() ? [] : [`${envName} is required for credentialRef ${credentialRef}`];
121
- }
122
114
  const credential = getCredential(credentialRef, authPath);
123
115
  if (!credential) {
124
116
  return [`Credential not found: ${credentialRef}`];
@@ -128,65 +120,6 @@ function credentialIssues(provider, credentialRef, env, authPath) {
128
120
  }
129
121
  return [];
130
122
  }
131
- function readEnvModelConfig(env) {
132
- const provider = env.DAILY_BRIEF_MODEL_PROVIDER?.trim();
133
- if (!provider) {
134
- return undefined;
135
- }
136
- const normalizedProvider = normalizeProvider(provider, env);
137
- return {
138
- provider: normalizedProvider,
139
- model: env.DAILY_BRIEF_MODEL?.trim() || defaultModelForProvider(normalizedProvider),
140
- ...(env.DAILY_BRIEF_MODEL_BASE_URL?.trim() ? { baseUrl: env.DAILY_BRIEF_MODEL_BASE_URL.trim() } : {}),
141
- ...readEnvCredentialRef(env, normalizedProvider)
142
- };
143
- }
144
- function normalizeProvider(value, env) {
145
- const provider = value.trim().toLowerCase();
146
- if (provider === "codex" || provider === "hermes") {
147
- return "openai-codex";
148
- }
149
- if (provider === "faux") {
150
- if (env.DAILY_BRIEF_ALLOW_FAUX_PROVIDER === "true") {
151
- return provider;
152
- }
153
- throw new Error("DAILY_BRIEF_MODEL_PROVIDER=faux is only allowed when DAILY_BRIEF_ALLOW_FAUX_PROVIDER=true");
154
- }
155
- if (provider === "openai-codex" || provider === "openai" || provider === "deepseek" || provider === "openai-compatible") {
156
- return provider;
157
- }
158
- throw new Error(`Unsupported DAILY_BRIEF_MODEL_PROVIDER: ${value}`);
159
- }
160
- function defaultModelForProvider(provider) {
161
- if (provider === "openai-codex") {
162
- return "gpt-5.5";
163
- }
164
- if (provider === "openai") {
165
- return "gpt-4.1-mini";
166
- }
167
- if (provider === "deepseek") {
168
- return "deepseek-chat";
169
- }
170
- if (provider === "openai-compatible") {
171
- return "openai-compatible-model";
172
- }
173
- return "faux-daily-brief-renderer";
174
- }
175
- function defaultCredentialRefForProvider(provider) {
176
- if (provider === "faux") {
177
- return undefined;
178
- }
179
- if (provider === "openai") {
180
- return "env:OPENAI_API_KEY";
181
- }
182
- if (provider === "deepseek") {
183
- return "env:DEEPSEEK_API_KEY";
184
- }
185
- if (provider === "openai-compatible") {
186
- return "env:OPENAI_API_KEY";
187
- }
188
- return "openai-codex.default";
189
- }
190
123
  function persistRefreshedOAuthCredential(ref, credential, credentials, authPath) {
191
124
  const store = readCredentialStore(authPath);
192
125
  const previous = store.credentials[ref];
@@ -198,11 +131,6 @@ function persistRefreshedOAuthCredential(ref, credential, credentials, authPath)
198
131
  };
199
132
  writeCredentialStore(store, authPath);
200
133
  }
201
- function readEnvCredentialRef(env, provider) {
202
- const credentialRef = env.DAILY_BRIEF_MODEL_CREDENTIAL_REF?.trim() ||
203
- defaultCredentialRefForProvider(provider);
204
- return credentialRef ? { credentialRef } : {};
205
- }
206
134
  function promptForOAuth(io, message) {
207
135
  if (!io.prompt) {
208
136
  throw new Error(`OAuth login requires interactive input: ${message}`);