@dench.com/cli 2.2.3 → 2.2.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.
Files changed (3) hide show
  1. package/ads.ts +63 -47
  2. package/crm.ts +5 -1
  3. package/package.json +1 -1
package/ads.ts CHANGED
@@ -936,9 +936,16 @@ function spinnerVerbFromCache(): string | null {
936
936
  function refreshClaudeSpinnerVerb(): void {
937
937
  if (!claudeInstalled()) return;
938
938
  const verb = spinnerVerbFromCache();
939
- if (!verb) return;
940
939
  const settings = readJson<Record<string, unknown>>(CLAUDE_SETTINGS, {});
941
- settings.spinnerVerbs = { mode: "replace", verbs: [verb] };
940
+ if (verb) {
941
+ settings.spinnerVerbs = { mode: "replace", verbs: [verb] };
942
+ } else if (settings.spinnerVerbs) {
943
+ // No live ad → drop the sponsored thinking verb so Claude Code stops
944
+ // showing a stale ad on every spin.
945
+ delete settings.spinnerVerbs;
946
+ } else {
947
+ return;
948
+ }
942
949
  atomicWrite(CLAUDE_SETTINGS, `${JSON.stringify(settings, null, 2)}\n`);
943
950
  }
944
951
 
@@ -1138,47 +1145,26 @@ function shellSnippet(
1138
1145
  ): string {
1139
1146
  if (kind === "fish") {
1140
1147
  return `${SHELL_MARK_START}
1141
- # Prints one sponsored line above each prompt shows in any terminal
1142
- # (Codex, Cursor, openclaw, hermes, …). Remove this block to disable.
1143
- function _dench_ads
1144
- set -l line (${nodeBin()} ${RENDER_SCRIPT} 2>/dev/null | string collect)
1145
- test -n "$line"; and printf '%s\\n' "$line"
1146
- end
1147
- if not functions -q _dench_ads_original_prompt
1148
- if functions -q fish_prompt
1149
- functions -c fish_prompt _dench_ads_original_prompt
1150
- else if functions -q fish_default_prompt
1151
- functions -c fish_default_prompt _dench_ads_original_prompt
1152
- end
1153
- end
1154
- if functions -q _dench_ads_original_prompt
1155
- function fish_prompt
1156
- _dench_ads
1157
- _dench_ads_original_prompt
1158
- end
1159
- else
1160
- function fish_prompt
1161
- _dench_ads
1162
- end
1148
+ # Prints one sponsored line ONCE when a new interactive shell opens (not on
1149
+ # every prompt) — shows in any terminal (Codex, Cursor, plain shell, …).
1150
+ # Remove this block to disable.
1151
+ if status is-interactive
1152
+ set -l _dench_line (${nodeBin()} ${RENDER_SCRIPT} 2>/dev/null | string collect)
1153
+ test -n "$_dench_line"; and printf '%s\\n' "$_dench_line"
1163
1154
  end
1164
1155
  ${SHELL_MARK_END}`;
1165
1156
  }
1166
1157
  return `${SHELL_MARK_START}
1167
- # Prints one sponsored line above each prompt shows in any terminal
1168
- # (Codex, Cursor, openclaw, hermes, …). Remove this block to disable.
1169
- _dench_ads() {
1170
- local line
1171
- line="$(${nodeBin()} ${RENDER_SCRIPT} 2>/dev/null)"
1172
- [ -n "$line" ] && printf '%s\\n' "$line"
1173
- }
1174
- if [ -n "$ZSH_VERSION" ]; then
1175
- autoload -Uz add-zsh-hook 2>/dev/null && add-zsh-hook precmd _dench_ads
1176
- elif [ -n "$BASH_VERSION" ]; then
1177
- case "$PROMPT_COMMAND" in
1178
- *_dench_ads*) ;;
1179
- *) PROMPT_COMMAND="_dench_ads;$PROMPT_COMMAND" ;;
1180
- esac
1181
- fi
1158
+ # Prints one sponsored line ONCE when a new interactive shell opens (not on
1159
+ # every prompt) — shows in any terminal (Codex, Cursor, plain shell, …).
1160
+ # Remove this block to disable.
1161
+ case "$-" in
1162
+ *i*)
1163
+ _dench_line="$(${nodeBin()} ${RENDER_SCRIPT} 2>/dev/null)"
1164
+ [ -n "$_dench_line" ] && printf '%s\\n' "$_dench_line"
1165
+ unset _dench_line
1166
+ ;;
1167
+ esac
1182
1168
  ${SHELL_MARK_END}`;
1183
1169
  }
1184
1170
 
@@ -1595,7 +1581,7 @@ async function refresh(authToken?: string): Promise<string> {
1595
1581
  saveAdSource(); // let the detached background worker know where to refetch
1596
1582
  let ad: Ad | null = null;
1597
1583
  let flushed = 0;
1598
- let src: "live" | "demo" = "demo";
1584
+ let src: "live" | "demo" | "none" = "none";
1599
1585
  let keepExistingCache = false;
1600
1586
  if (endpoint) {
1601
1587
  const previousAd = loadAd({ allowExpired: true });
@@ -1604,14 +1590,33 @@ async function refresh(authToken?: string): Promise<string> {
1604
1590
  const fetched = await fetchAd(endpoint);
1605
1591
  ad = fetched.ad;
1606
1592
  flushed = await flushImpressions(endpoint, authToken, ad ?? previousAd);
1607
- if (ad) src = "live";
1608
- else if (previousAd) {
1609
- ad = previousAd;
1610
- src = isDemoAdId(previousAd.id) ? "demo" : "live";
1611
- keepExistingCache = true;
1593
+ if (ad) {
1594
+ src = "live";
1595
+ } else if (previousAd && !isDemoAdId(previousAd.id)) {
1596
+ // The endpoint had no campaign this poll — keep a recently-live creative
1597
+ // briefly so the line doesn't flicker between fetches, but ONLY if it
1598
+ // hasn't expired. Past expiry with no live campaign → clear it (below).
1599
+ if (previousAd.expiresAt && Date.now() <= previousAd.expiresAt) {
1600
+ ad = previousAd;
1601
+ src = "live";
1602
+ keepExistingCache = true;
1603
+ }
1612
1604
  }
1605
+ // CRITICAL: when pointed at a real endpoint that returns NO campaign, show
1606
+ // NOTHING. Never fall back to bundled demo ads in production — that would
1607
+ // display a fake "Linear"/"Vercel" line nobody paid for. Demo ads are only
1608
+ // for the no-endpoint (offline/local) path below.
1609
+ if (!ad) {
1610
+ clearAdCache();
1611
+ refreshClaudeSpinnerVerb();
1612
+ return "No live campaign — nothing to show.";
1613
+ }
1614
+ } else {
1615
+ // No endpoint configured at all (offline/dev) → bundled demo rotation so
1616
+ // the feature is demonstrable with zero backend.
1617
+ ad = pickDemoAd();
1618
+ src = "demo";
1613
1619
  }
1614
- if (!ad) ad = pickDemoAd();
1615
1620
  if (!keepExistingCache) {
1616
1621
  ad.expiresAt = Date.now() + DEFAULT_TTL_MS;
1617
1622
  atomicWrite(AD_CACHE, JSON.stringify(ad));
@@ -1622,6 +1627,17 @@ async function refresh(authToken?: string): Promise<string> {
1622
1627
  return `Cached ${src} ad: ${ad.label}${flushMsg}`;
1623
1628
  }
1624
1629
 
1630
+ // Remove the cached ad so the hooks/render print nothing when no campaign is
1631
+ // live. Best-effort; the render script already treats a missing cache as
1632
+ // "print nothing".
1633
+ function clearAdCache(): void {
1634
+ try {
1635
+ if (existsSync(AD_CACHE)) unlinkSync(AD_CACHE);
1636
+ } catch {
1637
+ // best-effort
1638
+ }
1639
+ }
1640
+
1625
1641
  // ---------------------------------------------------------------------------
1626
1642
  // status
1627
1643
  // ---------------------------------------------------------------------------
@@ -1998,7 +2014,7 @@ Usage:
1998
2014
  claude → Claude Code's official statusLine hook (~/.claude/settings.json)
1999
2015
  codex → Codex CLI lifecycle hooks (~/.codex/hooks.json); shows the
2000
2016
  line as a systemMessage on SessionStart + after each turn
2001
- shell → a precmd line for any terminal (zsh/bash/fish); needs --apply
2017
+ shell → one line at shell startup, any terminal (zsh/bash/fish); needs --apply
2002
2018
  agentmd → append an instruction block to ./CLAUDE.md and ./AGENTS.md
2003
2019
  so the agent runs \`dench ads show\` during its turn
2004
2020
  all → all of the above
package/crm.ts CHANGED
@@ -2755,10 +2755,14 @@ Fields (columns):
2755
2755
  Appends <value> to the field's enumValues. --color is a hex like
2756
2756
  "#22c55e"; --position is a 0-based insertion index (defaults to append).
2757
2757
  Idempotent: re-adding an existing value only updates its color when given.
2758
+ For a board-backed Status/Stage field this also creates the matching
2759
+ kanban column (positioned to match), so you do NOT need a separate
2760
+ "statuses set" call.
2758
2761
  dench crm fields enum-remove <object> <field> <value>
2759
2762
  Removes the value from the option list. Historical entry rows that held
2760
2763
  <value> keep the literal string (the badge falls back to the default
2761
- tint), matching Notion's "remove option, keep history" semantics.
2764
+ tint), matching Notion's "remove option, keep history" semantics. A
2765
+ matching kanban column is removed too.
2762
2766
  dench crm fields enum-rename <object> <field> <oldValue> <newValue>
2763
2767
  Renames the option AND rewrites every entry that referenced the old
2764
2768
  value (bounded to 1000 rows per call) AND renames matching crmStatuses.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dench.com/cli",
3
- "version": "2.2.3",
3
+ "version": "2.2.5",
4
4
  "description": "Dench agent workspace CLI. v2 unifies auth behind `dench signin`; the legacy `dench login` / `dench onboard` / `dench setup` / `dench what-can-i-do` / `dench register` commands now error with a redirect.",
5
5
  "type": "module",
6
6
  "bin": {