@claude-flow/cli 3.25.6 → 3.26.1
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/.claude/.proven-config-version +1 -0
- package/.claude/helpers/.helpers-version +1 -1
- package/.claude/helpers/helpers.manifest.json +5 -4
- package/.claude/helpers/hook-handler.cjs +129 -0
- package/.claude/helpers/statusline.cjs +781 -505
- package/.claude/proven-config.json +42 -0
- package/catalog-manifest.json +12 -0
- package/dist/src/commands/advisor.d.ts +15 -0
- package/dist/src/commands/advisor.js +94 -0
- package/dist/src/commands/doctor.js +77 -3
- package/dist/src/commands/funnel.d.ts +13 -0
- package/dist/src/commands/funnel.js +103 -0
- package/dist/src/commands/hooks.js +112 -46
- package/dist/src/commands/index.js +10 -0
- package/dist/src/commands/init.js +44 -0
- package/dist/src/commands/proxy.d.ts +21 -0
- package/dist/src/commands/proxy.js +310 -0
- package/dist/src/commands/security.js +29 -0
- package/dist/src/commands/settings.d.ts +19 -0
- package/dist/src/commands/settings.js +180 -0
- package/dist/src/commands/version.d.ts +42 -0
- package/dist/src/commands/version.js +106 -0
- package/dist/src/funnel/advisor-tip.d.ts +58 -0
- package/dist/src/funnel/advisor-tip.js +92 -0
- package/dist/src/funnel/attribution.d.ts +37 -0
- package/dist/src/funnel/attribution.js +101 -0
- package/dist/src/funnel/consent.d.ts +22 -0
- package/dist/src/funnel/consent.js +55 -0
- package/dist/src/funnel/credit-errors.d.ts +31 -0
- package/dist/src/funnel/credit-errors.js +88 -0
- package/dist/src/funnel/credit-notifier.d.ts +44 -0
- package/dist/src/funnel/credit-notifier.js +74 -0
- package/dist/src/funnel/disclosure.d.ts +46 -0
- package/dist/src/funnel/disclosure.js +97 -0
- package/dist/src/funnel/enrollment.d.ts +36 -0
- package/dist/src/funnel/enrollment.js +64 -0
- package/dist/src/funnel/environment.d.ts +17 -0
- package/dist/src/funnel/environment.js +39 -0
- package/dist/src/funnel/event-transport.d.ts +51 -0
- package/dist/src/funnel/event-transport.js +199 -0
- package/dist/src/funnel/events.d.ts +42 -0
- package/dist/src/funnel/events.js +150 -0
- package/dist/src/funnel/index.d.ts +20 -0
- package/dist/src/funnel/index.js +20 -0
- package/dist/src/funnel/insights.d.ts +49 -0
- package/dist/src/funnel/insights.js +120 -0
- package/dist/src/funnel/local-signals.d.ts +15 -0
- package/dist/src/funnel/local-signals.js +68 -0
- package/dist/src/funnel/message-transport.d.ts +51 -0
- package/dist/src/funnel/message-transport.js +149 -0
- package/dist/src/funnel/messages.d.ts +55 -0
- package/dist/src/funnel/messages.js +160 -0
- package/dist/src/funnel/power-saver-notifier.d.ts +44 -0
- package/dist/src/funnel/power-saver-notifier.js +92 -0
- package/dist/src/funnel/precedence.d.ts +16 -0
- package/dist/src/funnel/precedence.js +85 -0
- package/dist/src/funnel/promo.d.ts +41 -0
- package/dist/src/funnel/promo.js +144 -0
- package/dist/src/funnel/rate-limit-notifier.d.ts +55 -0
- package/dist/src/funnel/rate-limit-notifier.js +102 -0
- package/dist/src/funnel/rotation.d.ts +19 -0
- package/dist/src/funnel/rotation.js +70 -0
- package/dist/src/funnel/state.d.ts +13 -0
- package/dist/src/funnel/state.js +52 -0
- package/dist/src/funnel/toggle-cooldown.d.ts +17 -0
- package/dist/src/funnel/toggle-cooldown.js +32 -0
- package/dist/src/funnel/types.d.ts +86 -0
- package/dist/src/funnel/types.js +26 -0
- package/dist/src/init/executor.js +9 -3
- package/dist/src/init/helper-refresh.d.ts +33 -2
- package/dist/src/init/helper-refresh.js +102 -12
- package/dist/src/init/helpers-generator.js +52 -0
- package/dist/src/init/statusline-generator.js +292 -116
- package/dist/src/services/daemon-autostart.js +28 -5
- package/dist/src/services/fable-harness.d.ts +39 -0
- package/dist/src/services/fable-harness.js +41 -0
- package/package.json +13 -4
- package/dist/src/ruvector/lattice-wasm.d.ts +0 -14
- package/dist/src/ruvector/lattice-wasm.js +0 -144
- package/plugins/ruflo-metaharness/.claude-flow/daemon-state.json +0 -178
- package/plugins/ruflo-metaharness/.claude-flow/daemon.pid +0 -1
- package/plugins/ruflo-metaharness/.claude-flow/data/pending-insights.jsonl +0 -5
- package/plugins/ruflo-metaharness/.claude-flow/logs/daemon.log +0 -43
- package/plugins/ruflo-metaharness/.claude-flow/logs/headless/audit_1783604774864_ozbujc_prompt.log +0 -19
- package/plugins/ruflo-metaharness/.claude-flow/logs/headless/audit_1783604774864_ozbujc_result.log +0 -108
- package/plugins/ruflo-metaharness/.claude-flow/logs/headless/audit_1783605513587_ulvmpb_prompt.log +0 -19
- package/plugins/ruflo-metaharness/.claude-flow/logs/headless/audit_1783605513587_ulvmpb_result.log +0 -209
- package/plugins/ruflo-metaharness/.claude-flow/logs/headless/audit_1783606368867_ahysui_prompt.log +0 -19
- package/plugins/ruflo-metaharness/.claude-flow/logs/headless/audit_1783606368867_ahysui_result.log +0 -192
- package/plugins/ruflo-metaharness/.claude-flow/logs/headless/optimize_1783604894861_v6n3ut_prompt.log +0 -14
- package/plugins/ruflo-metaharness/.claude-flow/logs/headless/optimize_1783604894861_v6n3ut_result.log +0 -66
- package/plugins/ruflo-metaharness/.claude-flow/logs/headless/optimize_1783605934532_9h8ikb_prompt.log +0 -14
- package/plugins/ruflo-metaharness/.claude-flow/logs/headless/optimize_1783605934532_9h8ikb_result.log +0 -68
- package/plugins/ruflo-metaharness/.claude-flow/logs/headless/testgaps_1783605134860_9jssz9_prompt.log +0 -14
- package/plugins/ruflo-metaharness/.claude-flow/logs/headless/testgaps_1783605134860_9jssz9_result.log +0 -69
- package/plugins/ruflo-metaharness/.claude-flow/logs/headless/testgaps_1783606516743_zftbaa_prompt.log +0 -14
- package/plugins/ruflo-metaharness/.claude-flow/metrics/backup.json +0 -7
- package/plugins/ruflo-metaharness/.claude-flow/metrics/codebase-map.json +0 -11
- package/plugins/ruflo-metaharness/.claude-flow/metrics/consolidation.json +0 -16
- package/plugins/ruflo-metaharness/.claude-flow/metrics/harness-loop.json +0 -83
- package/plugins/ruflo-metaharness/.claude-flow/metrics/performance.json +0 -67
- package/plugins/ruflo-metaharness/.claude-flow/metrics/security-audit.json +0 -191
- package/plugins/ruflo-metaharness/.claude-flow/metrics/test-gaps.json +0 -68
- package/plugins/ruflo-metaharness/.claude-flow/neural/stats.json +0 -6
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `ruflo version` — ANV (Agent-Native Versioning) Phase 1.
|
|
3
|
+
* https://gist.github.com/ruvnet/0d858ad440a4439b4a2281a40c39b1a0
|
|
4
|
+
*
|
|
5
|
+
* Plain `ruflo --version` / `-V` (index.ts's showVersion()) is UNCHANGED —
|
|
6
|
+
* it stays bare semver so scripts parsing that output never see a surprise
|
|
7
|
+
* suffix. This is a separate subcommand: `ruflo version` prints the same
|
|
8
|
+
* bare semver by default, and `--explain` additionally renders the ANV
|
|
9
|
+
* catalog/benchmark breakdown when a catalog-manifest.json ships with this
|
|
10
|
+
* install. No catalog-manifest.json (e.g. an old install, or a dev checkout
|
|
11
|
+
* that hasn't run the generator) degrades to bare semver — the suffix is
|
|
12
|
+
* advisory, never load-bearing for npm range resolution or CLI behavior.
|
|
13
|
+
*/
|
|
14
|
+
import type { Command } from '../types.js';
|
|
15
|
+
interface CatalogManifest {
|
|
16
|
+
schemaVersion: number;
|
|
17
|
+
generation: number;
|
|
18
|
+
generatedAt: string;
|
|
19
|
+
gitSha: string;
|
|
20
|
+
catalog: {
|
|
21
|
+
agents: number;
|
|
22
|
+
tools: number;
|
|
23
|
+
skills: number;
|
|
24
|
+
};
|
|
25
|
+
benchmark: {
|
|
26
|
+
tier: number;
|
|
27
|
+
verifiedAt: string;
|
|
28
|
+
signature?: string;
|
|
29
|
+
} | null;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* The advisory suffix itself (semver build metadata, `+` prefix — legal per
|
|
33
|
+
* semver.org §10, ignored by npm for range resolution/precedence).
|
|
34
|
+
* +ad.<release-sequence>.g<gitSha>.cat<generation>[.hal<tier>]
|
|
35
|
+
* `.hal<tier>` is only appended when a real, signed benchmark submission
|
|
36
|
+
* exists for THIS catalog generation — never fabricated (ANV's own
|
|
37
|
+
* "verifiable, no unverifiable claims" principle).
|
|
38
|
+
*/
|
|
39
|
+
export declare function buildAdvisorySuffix(manifest: CatalogManifest, releaseSequence?: number): string;
|
|
40
|
+
export declare const versionCommand: Command;
|
|
41
|
+
export {};
|
|
42
|
+
//# sourceMappingURL=version.d.ts.map
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { output } from '../output.js';
|
|
2
|
+
import { existsSync, readFileSync } from 'fs';
|
|
3
|
+
import { join, dirname } from 'path';
|
|
4
|
+
import { fileURLToPath } from 'url';
|
|
5
|
+
import { createRequire } from 'module';
|
|
6
|
+
import { getInstalledCliVersion } from '../init/helper-refresh.js';
|
|
7
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
/** Locate catalog-manifest.json next to the installed package root. */
|
|
9
|
+
function findCatalogManifest() {
|
|
10
|
+
const candidates = [];
|
|
11
|
+
try {
|
|
12
|
+
const esmRequire = createRequire(import.meta.url);
|
|
13
|
+
const pkgRoot = dirname(esmRequire.resolve('@claude-flow/cli/package.json'));
|
|
14
|
+
candidates.push(join(pkgRoot, 'catalog-manifest.json'));
|
|
15
|
+
}
|
|
16
|
+
catch { /* not resolvable via package resolution */ }
|
|
17
|
+
// Dev checkout fallback: this compiled file lives at dist/src/commands/version.js
|
|
18
|
+
// (three levels under the package root) when built, or is run via tsx one level
|
|
19
|
+
// shallower from src/commands/version.ts — try both.
|
|
20
|
+
candidates.push(join(__dirname, '..', '..', 'catalog-manifest.json'));
|
|
21
|
+
candidates.push(join(__dirname, '..', '..', '..', 'catalog-manifest.json'));
|
|
22
|
+
for (const c of candidates) {
|
|
23
|
+
if (!existsSync(c))
|
|
24
|
+
continue;
|
|
25
|
+
try {
|
|
26
|
+
const parsed = JSON.parse(readFileSync(c, 'utf-8'));
|
|
27
|
+
if (parsed && typeof parsed.generation === 'number' && parsed.catalog)
|
|
28
|
+
return parsed;
|
|
29
|
+
}
|
|
30
|
+
catch { /* corrupt manifest — treat as absent */ }
|
|
31
|
+
}
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* The advisory suffix itself (semver build metadata, `+` prefix — legal per
|
|
36
|
+
* semver.org §10, ignored by npm for range resolution/precedence).
|
|
37
|
+
* +ad.<release-sequence>.g<gitSha>.cat<generation>[.hal<tier>]
|
|
38
|
+
* `.hal<tier>` is only appended when a real, signed benchmark submission
|
|
39
|
+
* exists for THIS catalog generation — never fabricated (ANV's own
|
|
40
|
+
* "verifiable, no unverifiable claims" principle).
|
|
41
|
+
*/
|
|
42
|
+
export function buildAdvisorySuffix(manifest, releaseSequence = 1) {
|
|
43
|
+
const parts = [`ad.${releaseSequence}`, `g${manifest.gitSha}`, `cat${manifest.generation}`];
|
|
44
|
+
if (manifest.benchmark)
|
|
45
|
+
parts.push(`hal${manifest.benchmark.tier}`);
|
|
46
|
+
return `+${parts.join('.')}`;
|
|
47
|
+
}
|
|
48
|
+
export const versionCommand = {
|
|
49
|
+
name: 'version',
|
|
50
|
+
description: 'Show installed version, with --explain for the ANV catalog breakdown',
|
|
51
|
+
options: [
|
|
52
|
+
{
|
|
53
|
+
name: 'explain',
|
|
54
|
+
description: 'Show the full ANV breakdown (catalog generation, counts, benchmark status)',
|
|
55
|
+
type: 'boolean',
|
|
56
|
+
default: false,
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
name: 'require-catalog-gte',
|
|
60
|
+
description: 'Exit non-zero unless the installed catalog generation is >= N (capability gating for scripts)',
|
|
61
|
+
type: 'number',
|
|
62
|
+
},
|
|
63
|
+
],
|
|
64
|
+
examples: [
|
|
65
|
+
{ command: 'ruflo version', description: 'Print the installed semver' },
|
|
66
|
+
{ command: 'ruflo version --explain', description: 'Print the full ANV catalog/benchmark breakdown' },
|
|
67
|
+
{ command: 'ruflo version --require-catalog-gte 40', description: 'Gate a script on a minimum catalog generation' },
|
|
68
|
+
],
|
|
69
|
+
action: async (ctx) => {
|
|
70
|
+
const version = getInstalledCliVersion();
|
|
71
|
+
const manifest = findCatalogManifest();
|
|
72
|
+
const requireGte = ctx.flags.requireCatalogGte;
|
|
73
|
+
if (typeof requireGte === 'number') {
|
|
74
|
+
const generation = manifest?.generation ?? 0;
|
|
75
|
+
if (generation >= requireGte) {
|
|
76
|
+
output.writeln(`OK (installed catalog is ${generation})`);
|
|
77
|
+
return { success: true };
|
|
78
|
+
}
|
|
79
|
+
output.printError(`Installed catalog generation ${generation} is below required ${requireGte}`);
|
|
80
|
+
return { success: false };
|
|
81
|
+
}
|
|
82
|
+
if (!ctx.flags.explain) {
|
|
83
|
+
output.writeln(`${version}`);
|
|
84
|
+
return { success: true };
|
|
85
|
+
}
|
|
86
|
+
if (!manifest) {
|
|
87
|
+
output.writeln(`Installed: ruflo@${version}`);
|
|
88
|
+
output.writeln(output.dim(' (no catalog-manifest.json — plain semver, pre-ANV or dev checkout)'));
|
|
89
|
+
return { success: true };
|
|
90
|
+
}
|
|
91
|
+
const suffix = buildAdvisorySuffix(manifest);
|
|
92
|
+
output.writeln(`Installed: ${output.bold(`ruflo@${version}${suffix}`)}`);
|
|
93
|
+
output.writeln();
|
|
94
|
+
output.writeln(`Era: AD (Agent Descent) — 1st generation`);
|
|
95
|
+
output.writeln(`Catalog: generation ${manifest.generation} ` +
|
|
96
|
+
`(agents: ${manifest.catalog.agents} types, tools: ${manifest.catalog.tools} MCP, skills: ${manifest.catalog.skills})`);
|
|
97
|
+
if (manifest.benchmark) {
|
|
98
|
+
output.writeln(`Benchmark: GAIA tier ${manifest.benchmark.tier} (verified ${manifest.benchmark.verifiedAt.slice(0, 10)}, signed)`);
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
output.writeln(output.dim('Benchmark: not yet submitted (no verified GAIA/HAL score for this catalog generation)'));
|
|
102
|
+
}
|
|
103
|
+
return { success: true };
|
|
104
|
+
},
|
|
105
|
+
};
|
|
106
|
+
//# sourceMappingURL=version.js.map
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Advisor co-pilot tip — ADR-316. A periodic, consent-gated, budget-capped
|
|
3
|
+
* suggestion generated by ruflo's existing Fable Advisor Harness
|
|
4
|
+
* (services/fable-harness.ts, ADR-172), surfaced in the local insight
|
|
5
|
+
* ticker (insights.ts) alongside the CVE/uncommitted/power-saver signals.
|
|
6
|
+
*
|
|
7
|
+
* This is the ONE insight source that spends real money and makes a real
|
|
8
|
+
* network call — every other source in insights.ts is synchronous, local,
|
|
9
|
+
* $0 by construction. Three hard gates keep that safe:
|
|
10
|
+
* 1. consent: 'advisor-tips' domain (types.ts/consent.ts), never bundled
|
|
11
|
+
* with anything else, off by default.
|
|
12
|
+
* 2. cooldown: at most once per ADVISOR_REFRESH_TTL_MS regardless of how
|
|
13
|
+
* many sessions run in that window — a file-based TTL checked BEFORE
|
|
14
|
+
* spawning, not a per-call budget hope.
|
|
15
|
+
* 3. budget: FableHarness's own --max-budget-usd hard cap, passed through
|
|
16
|
+
* to the underlying `claude -p` call.
|
|
17
|
+
*
|
|
18
|
+
* The snapshot sent to the model is built ONLY from the same structural
|
|
19
|
+
* signals insights.ts already uses (funnel/local-signals.ts) — never raw
|
|
20
|
+
* prompts, commands, or file contents. Matches ADR-309's "no raw content,
|
|
21
|
+
* ever" bar even though ADR-309 is about telemetry and this is a different,
|
|
22
|
+
* separately-consented data flow (an opt-in advisor query, not analytics).
|
|
23
|
+
*
|
|
24
|
+
* Cheapness/latency discipline for the CALLER (never this module itself):
|
|
25
|
+
* refreshAdvisorTipIfStale performs a real network-bound `claude -p` spawn
|
|
26
|
+
* when stale — callers MUST run it from a properly-awaited, DETACHED
|
|
27
|
+
* background process (mirroring the funnel-cache fix's
|
|
28
|
+
* spawnDetachedFunnelRefresh pattern), never inline in a statusline render.
|
|
29
|
+
*/
|
|
30
|
+
import { FableHarness, type CoPilotSnapshot } from '../services/fable-harness.js';
|
|
31
|
+
/** At most one real spend per real day, regardless of session count. */
|
|
32
|
+
export declare const ADVISOR_REFRESH_TTL_MS: number;
|
|
33
|
+
/** Conservative default — one adviseCoPilotTip call is a single, unbatched
|
|
34
|
+
* item, so the ~$0.34 clean-cwd anchor (not the $0.02 batched anchor)
|
|
35
|
+
* applies. Override via RUFLO_ADVISOR_MAX_BUDGET_USD for advanced users. */
|
|
36
|
+
export declare const ADVISOR_DEFAULT_BUDGET_USD = 0.4;
|
|
37
|
+
export interface CachedAdvisorTip {
|
|
38
|
+
headline: string;
|
|
39
|
+
detail: string;
|
|
40
|
+
}
|
|
41
|
+
/** Synchronous, $0, no network — safe to call from the insight ticker's hot path. */
|
|
42
|
+
export declare function readAdvisorTip(now?: Date): CachedAdvisorTip | null;
|
|
43
|
+
export interface AdvisorRefreshResult {
|
|
44
|
+
refreshed: boolean;
|
|
45
|
+
reason?: 'not-consented' | 'fresh' | 'no-tip' | 'error';
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Refresh the cached advisor tip if consent is granted AND the cache is
|
|
49
|
+
* stale. Spends real money via FableHarness exactly once per call site,
|
|
50
|
+
* gated by the TTL check above — callers get to decide cadence (e.g.
|
|
51
|
+
* "call this on every session-restore"; the TTL makes repeated calls cheap
|
|
52
|
+
* no-ops within the same window).
|
|
53
|
+
*/
|
|
54
|
+
export declare function refreshAdvisorTipIfStale(snapshot: CoPilotSnapshot, opts?: {
|
|
55
|
+
now?: Date;
|
|
56
|
+
harness?: FableHarness;
|
|
57
|
+
}): Promise<AdvisorRefreshResult>;
|
|
58
|
+
//# sourceMappingURL=advisor-tip.d.ts.map
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Advisor co-pilot tip — ADR-316. A periodic, consent-gated, budget-capped
|
|
3
|
+
* suggestion generated by ruflo's existing Fable Advisor Harness
|
|
4
|
+
* (services/fable-harness.ts, ADR-172), surfaced in the local insight
|
|
5
|
+
* ticker (insights.ts) alongside the CVE/uncommitted/power-saver signals.
|
|
6
|
+
*
|
|
7
|
+
* This is the ONE insight source that spends real money and makes a real
|
|
8
|
+
* network call — every other source in insights.ts is synchronous, local,
|
|
9
|
+
* $0 by construction. Three hard gates keep that safe:
|
|
10
|
+
* 1. consent: 'advisor-tips' domain (types.ts/consent.ts), never bundled
|
|
11
|
+
* with anything else, off by default.
|
|
12
|
+
* 2. cooldown: at most once per ADVISOR_REFRESH_TTL_MS regardless of how
|
|
13
|
+
* many sessions run in that window — a file-based TTL checked BEFORE
|
|
14
|
+
* spawning, not a per-call budget hope.
|
|
15
|
+
* 3. budget: FableHarness's own --max-budget-usd hard cap, passed through
|
|
16
|
+
* to the underlying `claude -p` call.
|
|
17
|
+
*
|
|
18
|
+
* The snapshot sent to the model is built ONLY from the same structural
|
|
19
|
+
* signals insights.ts already uses (funnel/local-signals.ts) — never raw
|
|
20
|
+
* prompts, commands, or file contents. Matches ADR-309's "no raw content,
|
|
21
|
+
* ever" bar even though ADR-309 is about telemetry and this is a different,
|
|
22
|
+
* separately-consented data flow (an opt-in advisor query, not analytics).
|
|
23
|
+
*
|
|
24
|
+
* Cheapness/latency discipline for the CALLER (never this module itself):
|
|
25
|
+
* refreshAdvisorTipIfStale performs a real network-bound `claude -p` spawn
|
|
26
|
+
* when stale — callers MUST run it from a properly-awaited, DETACHED
|
|
27
|
+
* background process (mirroring the funnel-cache fix's
|
|
28
|
+
* spawnDetachedFunnelRefresh pattern), never inline in a statusline render.
|
|
29
|
+
*/
|
|
30
|
+
import { hasConsent } from './consent.js';
|
|
31
|
+
import { readStateJson, writeStateJson } from './state.js';
|
|
32
|
+
import { FableHarness } from '../services/fable-harness.js';
|
|
33
|
+
const ADVISOR_CACHE_FILE = 'advisor-tip.json';
|
|
34
|
+
/** At most one real spend per real day, regardless of session count. */
|
|
35
|
+
export const ADVISOR_REFRESH_TTL_MS = 24 * 60 * 60 * 1000;
|
|
36
|
+
/** Conservative default — one adviseCoPilotTip call is a single, unbatched
|
|
37
|
+
* item, so the ~$0.34 clean-cwd anchor (not the $0.02 batched anchor)
|
|
38
|
+
* applies. Override via RUFLO_ADVISOR_MAX_BUDGET_USD for advanced users. */
|
|
39
|
+
export const ADVISOR_DEFAULT_BUDGET_USD = 0.4;
|
|
40
|
+
/** Synchronous, $0, no network — safe to call from the insight ticker's hot path. */
|
|
41
|
+
export function readAdvisorTip(now = new Date()) {
|
|
42
|
+
const cache = readStateJson(ADVISOR_CACHE_FILE);
|
|
43
|
+
// `!cache._ts` would wrongly treat a legitimate epoch-zero timestamp as
|
|
44
|
+
// absent (0 is falsy) — check the type explicitly instead.
|
|
45
|
+
if (!cache || !cache.headline || typeof cache._ts !== 'number')
|
|
46
|
+
return null;
|
|
47
|
+
if (now.getTime() - cache._ts >= ADVISOR_REFRESH_TTL_MS)
|
|
48
|
+
return null;
|
|
49
|
+
return { headline: cache.headline, detail: cache.detail ?? '' };
|
|
50
|
+
}
|
|
51
|
+
function isCacheStale(now) {
|
|
52
|
+
const cache = readStateJson(ADVISOR_CACHE_FILE);
|
|
53
|
+
if (!cache || typeof cache._ts !== 'number')
|
|
54
|
+
return true;
|
|
55
|
+
return now.getTime() - cache._ts >= ADVISOR_REFRESH_TTL_MS;
|
|
56
|
+
}
|
|
57
|
+
function resolveBudgetUsd() {
|
|
58
|
+
const raw = process.env.RUFLO_ADVISOR_MAX_BUDGET_USD;
|
|
59
|
+
const n = raw !== undefined ? Number(raw) : NaN;
|
|
60
|
+
return Number.isFinite(n) && n > 0 ? n : ADVISOR_DEFAULT_BUDGET_USD;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Refresh the cached advisor tip if consent is granted AND the cache is
|
|
64
|
+
* stale. Spends real money via FableHarness exactly once per call site,
|
|
65
|
+
* gated by the TTL check above — callers get to decide cadence (e.g.
|
|
66
|
+
* "call this on every session-restore"; the TTL makes repeated calls cheap
|
|
67
|
+
* no-ops within the same window).
|
|
68
|
+
*/
|
|
69
|
+
export async function refreshAdvisorTipIfStale(snapshot, opts = {}) {
|
|
70
|
+
const now = opts.now ?? new Date();
|
|
71
|
+
if (!hasConsent('advisor-tips'))
|
|
72
|
+
return { refreshed: false, reason: 'not-consented' };
|
|
73
|
+
if (!isCacheStale(now))
|
|
74
|
+
return { refreshed: false, reason: 'fresh' };
|
|
75
|
+
const harness = opts.harness ?? new FableHarness({ maxBudgetUsd: resolveBudgetUsd() });
|
|
76
|
+
try {
|
|
77
|
+
const tip = await harness.adviseCoPilotTip(snapshot);
|
|
78
|
+
if (!tip) {
|
|
79
|
+
// The model looked and found nothing worth surfacing — still stamp
|
|
80
|
+
// _ts so we don't re-spend inside the same TTL window re-asking the
|
|
81
|
+
// same question and getting the same "nothing to say" answer.
|
|
82
|
+
writeStateJson(ADVISOR_CACHE_FILE, { _ts: now.getTime() });
|
|
83
|
+
return { refreshed: true, reason: 'no-tip' };
|
|
84
|
+
}
|
|
85
|
+
writeStateJson(ADVISOR_CACHE_FILE, { _ts: now.getTime(), headline: tip.headline, detail: tip.detail });
|
|
86
|
+
return { refreshed: true };
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
return { refreshed: false, reason: 'error' };
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
//# sourceMappingURL=advisor-tip.js.map
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export interface AttributionInput {
|
|
2
|
+
/** UTM `medium` — the surface (e.g. 'statusline', 'enrollment', 'exhaustion'). */
|
|
3
|
+
medium: string;
|
|
4
|
+
/** UTM `campaign` — the message kind ('disclosure' | 'promotional' | 'educational'). */
|
|
5
|
+
campaign: string;
|
|
6
|
+
/** UTM `content` — the specific message id ('promo-meta-llm-routing' etc.). */
|
|
7
|
+
content: string;
|
|
8
|
+
/** Static UTM `source` — always 'ruflo' but overridable for tests. */
|
|
9
|
+
source?: string;
|
|
10
|
+
/** Injectable for deterministic tests. Defaults to the live clock. */
|
|
11
|
+
now?: Date;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Return `url` with UTM parameters appended, and — when telemetry consent is
|
|
15
|
+
* granted — a `fid` query parameter carrying the pseudonymous funnel ID.
|
|
16
|
+
* Preserves any query parameters the base URL already carried.
|
|
17
|
+
*/
|
|
18
|
+
export declare function attributionUrl(url: string, input: AttributionInput): string;
|
|
19
|
+
/**
|
|
20
|
+
* Wrap the target URL in a server-side click-redirect so the analytics
|
|
21
|
+
* function fires a `promo_open` event + records coarse geo before 302ing to
|
|
22
|
+
* the real destination.
|
|
23
|
+
*
|
|
24
|
+
* Only applied to promotional messages (they have a real URL destination).
|
|
25
|
+
* Disclosure/educational rows call attributionUrl directly.
|
|
26
|
+
*
|
|
27
|
+
* The click endpoint URL structure is:
|
|
28
|
+
* https://funnel.ruv.io/v1/click/{messageId}?to=<utm-decorated-target>
|
|
29
|
+
*
|
|
30
|
+
* The server validates `to` against its own host allowlist, records the
|
|
31
|
+
* event, and 302s. If the click endpoint is unreachable at OSC 8 time,
|
|
32
|
+
* the browser falls back to the terminal's normal error page — but since
|
|
33
|
+
* this is a rare failure path and impressions are already recorded, the
|
|
34
|
+
* loss is bounded.
|
|
35
|
+
*/
|
|
36
|
+
export declare function clickTrackedUrl(messageId: string, targetUrl: string, input: AttributionInput): string;
|
|
37
|
+
//# sourceMappingURL=attribution.d.ts.map
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Attribution URL builder — appends UTM parameters and the pseudonymous funnel
|
|
3
|
+
* ID to outbound funnel links so cognitum.one traffic analytics can attribute
|
|
4
|
+
* a landing back to the surface, message, and campaign that produced it
|
|
5
|
+
* (ADR-305 measurement plane).
|
|
6
|
+
*
|
|
7
|
+
* Failure discipline (ADR-301 no-runtime-network invariant, Phase 1):
|
|
8
|
+
* - This function is PURE — it never makes a network call. Attribution is
|
|
9
|
+
* shipped as query params on a link the user's own browser resolves; the
|
|
10
|
+
* CLI has zero runtime dependency on cognitum.one being reachable.
|
|
11
|
+
* If the whole API is down, the OSC 8 label still renders correctly, the
|
|
12
|
+
* click still leaves the terminal, and only the landing page fails (which
|
|
13
|
+
* is a browser-visible problem, not a statusline problem).
|
|
14
|
+
* - If the base URL is malformed, we return it verbatim rather than
|
|
15
|
+
* synthesizing a broken analytics endpoint. The OSC 8 renderer then
|
|
16
|
+
* re-validates the host against its allowlist and, on failure, drops
|
|
17
|
+
* the escape entirely and shows the plain label — never a raw URL.
|
|
18
|
+
*
|
|
19
|
+
* Privacy discipline (ADR-309):
|
|
20
|
+
* - `fid` (the pseudonymous funnel ID) is appended only when telemetry
|
|
21
|
+
* consent is present. Without consent, getFunnelId() returns null and
|
|
22
|
+
* the URL carries only the UTM fields — same landing page, no join key.
|
|
23
|
+
* - The base URL must already be an allowlisted https target; the OSC 8
|
|
24
|
+
* renderer re-validates the host at render time regardless.
|
|
25
|
+
*/
|
|
26
|
+
import { getFunnelId } from './events.js';
|
|
27
|
+
/**
|
|
28
|
+
* Server-side click-redirect endpoint. When set, promotional URLs route
|
|
29
|
+
* through here so the server can record a `promo_open` event + coarse
|
|
30
|
+
* geo (from CF-IPCountry / X-Appengine-Country) before 302ing to the
|
|
31
|
+
* real cognitum.one / agentics.org / etc. target.
|
|
32
|
+
*/
|
|
33
|
+
const CLICK_ENDPOINT_BASE = process.env.RUFLO_FUNNEL_CLICK_ENDPOINT ?? 'https://funnel.ruv.io/v1/click';
|
|
34
|
+
/**
|
|
35
|
+
* Return `url` with UTM parameters appended, and — when telemetry consent is
|
|
36
|
+
* granted — a `fid` query parameter carrying the pseudonymous funnel ID.
|
|
37
|
+
* Preserves any query parameters the base URL already carried.
|
|
38
|
+
*/
|
|
39
|
+
export function attributionUrl(url, input) {
|
|
40
|
+
let parsed;
|
|
41
|
+
try {
|
|
42
|
+
parsed = new URL(url);
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
return url;
|
|
46
|
+
}
|
|
47
|
+
// Defense in depth: reject non-https schemes at the builder stage. The OSC 8
|
|
48
|
+
// renderer allowlist would drop them at render time too, but decorating a
|
|
49
|
+
// `javascript:` or `data:` URL with a valid-looking `fid` would still be a
|
|
50
|
+
// record we don't want to emit. Fail closed by returning the input verbatim.
|
|
51
|
+
if (parsed.protocol !== 'https:')
|
|
52
|
+
return url;
|
|
53
|
+
const params = parsed.searchParams;
|
|
54
|
+
params.set('utm_source', input.source ?? 'ruflo');
|
|
55
|
+
params.set('utm_medium', input.medium);
|
|
56
|
+
params.set('utm_campaign', input.campaign);
|
|
57
|
+
params.set('utm_content', input.content);
|
|
58
|
+
const fid = getFunnelId(input.now ?? new Date());
|
|
59
|
+
if (fid)
|
|
60
|
+
params.set('fid', fid);
|
|
61
|
+
return parsed.toString();
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Wrap the target URL in a server-side click-redirect so the analytics
|
|
65
|
+
* function fires a `promo_open` event + records coarse geo before 302ing to
|
|
66
|
+
* the real destination.
|
|
67
|
+
*
|
|
68
|
+
* Only applied to promotional messages (they have a real URL destination).
|
|
69
|
+
* Disclosure/educational rows call attributionUrl directly.
|
|
70
|
+
*
|
|
71
|
+
* The click endpoint URL structure is:
|
|
72
|
+
* https://funnel.ruv.io/v1/click/{messageId}?to=<utm-decorated-target>
|
|
73
|
+
*
|
|
74
|
+
* The server validates `to` against its own host allowlist, records the
|
|
75
|
+
* event, and 302s. If the click endpoint is unreachable at OSC 8 time,
|
|
76
|
+
* the browser falls back to the terminal's normal error page — but since
|
|
77
|
+
* this is a rare failure path and impressions are already recorded, the
|
|
78
|
+
* loss is bounded.
|
|
79
|
+
*/
|
|
80
|
+
export function clickTrackedUrl(messageId, targetUrl, input) {
|
|
81
|
+
const attributed = attributionUrl(targetUrl, input);
|
|
82
|
+
// If attribution rejected the URL (non-https, malformed), pass through
|
|
83
|
+
// verbatim — never smuggle a bad URL into the click endpoint.
|
|
84
|
+
if (attributed === targetUrl) {
|
|
85
|
+
try {
|
|
86
|
+
const p = new URL(targetUrl);
|
|
87
|
+
if (p.protocol !== 'https:')
|
|
88
|
+
return targetUrl;
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
return targetUrl;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
// Sanitize the message id — allowlist [a-z0-9-] so a malformed id can't
|
|
95
|
+
// shape-shift the click endpoint path.
|
|
96
|
+
if (!/^[a-z0-9][a-z0-9-]{0,63}$/i.test(messageId))
|
|
97
|
+
return attributed;
|
|
98
|
+
const params = new URLSearchParams({ to: attributed });
|
|
99
|
+
return `${CLICK_ENDPOINT_BASE}/${encodeURIComponent(messageId)}?${params.toString()}`;
|
|
100
|
+
}
|
|
101
|
+
//# sourceMappingURL=attribution.js.map
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Consent domains and versioned receipts — ADR-302.
|
|
3
|
+
*
|
|
4
|
+
* Four+ distinct decisions, never bundled: account, proxy-install,
|
|
5
|
+
* telemetry, cloud-routing, hosted-memory. A receipt is written on both
|
|
6
|
+
* grant AND decline (a decline is a decision, not an absence). A stale
|
|
7
|
+
* policyVersion means the consent must be re-asked before the capability
|
|
8
|
+
* activates — never silently carried forward.
|
|
9
|
+
*/
|
|
10
|
+
import type { ConsentDomain, ConsentFile, ConsentReceipt } from './types.js';
|
|
11
|
+
export declare const CONSENT_DOMAINS: ConsentDomain[];
|
|
12
|
+
export declare function readConsents(): ConsentFile;
|
|
13
|
+
export declare function getConsent(domain: ConsentDomain): ConsentReceipt;
|
|
14
|
+
/**
|
|
15
|
+
* Effective consent: granted AND at the current policy version. A receipt
|
|
16
|
+
* from an older policy version is treated as not-consented (re-ask, never
|
|
17
|
+
* carry forward).
|
|
18
|
+
*/
|
|
19
|
+
export declare function hasConsent(domain: ConsentDomain): boolean;
|
|
20
|
+
export declare function recordConsent(domain: ConsentDomain, granted: boolean, surface: string, now?: Date): ConsentReceipt;
|
|
21
|
+
export declare function revokeConsent(domain: ConsentDomain, surface: string): ConsentReceipt;
|
|
22
|
+
//# sourceMappingURL=consent.d.ts.map
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Consent domains and versioned receipts — ADR-302.
|
|
3
|
+
*
|
|
4
|
+
* Four+ distinct decisions, never bundled: account, proxy-install,
|
|
5
|
+
* telemetry, cloud-routing, hosted-memory. A receipt is written on both
|
|
6
|
+
* grant AND decline (a decline is a decision, not an absence). A stale
|
|
7
|
+
* policyVersion means the consent must be re-asked before the capability
|
|
8
|
+
* activates — never silently carried forward.
|
|
9
|
+
*/
|
|
10
|
+
import { CONSENT_POLICY_VERSION } from './types.js';
|
|
11
|
+
import { readStateJson, writeStateJson } from './state.js';
|
|
12
|
+
const CONSENT_FILE = 'consent.json';
|
|
13
|
+
export const CONSENT_DOMAINS = [
|
|
14
|
+
'account',
|
|
15
|
+
'proxy-install',
|
|
16
|
+
'telemetry',
|
|
17
|
+
'cloud-routing',
|
|
18
|
+
'hosted-memory',
|
|
19
|
+
'sponsored-downtime',
|
|
20
|
+
'power-saver',
|
|
21
|
+
'training-data-sharing',
|
|
22
|
+
'advisor-tips',
|
|
23
|
+
];
|
|
24
|
+
export function readConsents() {
|
|
25
|
+
return readStateJson(CONSENT_FILE) ?? {};
|
|
26
|
+
}
|
|
27
|
+
export function getConsent(domain) {
|
|
28
|
+
const file = readConsents();
|
|
29
|
+
return (file[domain] ?? { granted: false, policyVersion: CONSENT_POLICY_VERSION, at: null, surface: null });
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Effective consent: granted AND at the current policy version. A receipt
|
|
33
|
+
* from an older policy version is treated as not-consented (re-ask, never
|
|
34
|
+
* carry forward).
|
|
35
|
+
*/
|
|
36
|
+
export function hasConsent(domain) {
|
|
37
|
+
const r = getConsent(domain);
|
|
38
|
+
return r.granted === true && r.at !== null && r.policyVersion === CONSENT_POLICY_VERSION;
|
|
39
|
+
}
|
|
40
|
+
export function recordConsent(domain, granted, surface, now = new Date()) {
|
|
41
|
+
const file = readConsents();
|
|
42
|
+
const receipt = {
|
|
43
|
+
granted,
|
|
44
|
+
policyVersion: CONSENT_POLICY_VERSION,
|
|
45
|
+
at: now.toISOString(),
|
|
46
|
+
surface,
|
|
47
|
+
};
|
|
48
|
+
file[domain] = receipt;
|
|
49
|
+
writeStateJson(CONSENT_FILE, file);
|
|
50
|
+
return receipt;
|
|
51
|
+
}
|
|
52
|
+
export function revokeConsent(domain, surface) {
|
|
53
|
+
return recordConsent(domain, false, surface);
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=consent.js.map
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Credit-error taxonomy and fail-closed classifier — ADR-303.
|
|
3
|
+
*
|
|
4
|
+
* Adapters map machine-readable provider codes into the canonical
|
|
5
|
+
* CreditErrorCode enum. Message text is NEVER parsed. Anything unmapped
|
|
6
|
+
* stays unclassified (confidence 0) and falls through to the ordinary
|
|
7
|
+
* error path. Only COGNITUM_CREDIT_EXHAUSTED — asserted by the Cognitum
|
|
8
|
+
* ledger, the single credit authority — may trigger the funnel surface.
|
|
9
|
+
*/
|
|
10
|
+
import { type NormalizedCreditError } from './types.js';
|
|
11
|
+
export interface ProviderErrorLike {
|
|
12
|
+
/** Machine-readable provider error code (e.g. `error.type` / `error.code`). */
|
|
13
|
+
providerCode?: string;
|
|
14
|
+
/** HTTP status, when the transport exposes one. */
|
|
15
|
+
status?: number;
|
|
16
|
+
}
|
|
17
|
+
export declare function classifyCreditError(err: ProviderErrorLike): NormalizedCreditError;
|
|
18
|
+
export interface CreditPromptSession {
|
|
19
|
+
creditPromptShown: boolean;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* The ADR-303 gate, fail-closed and frequency-capped: fires only for a
|
|
23
|
+
* confident, non-retryable Cognitum-ledger exhaustion, at most once per
|
|
24
|
+
* session. Everything else falls through to the ordinary error path.
|
|
25
|
+
*/
|
|
26
|
+
export declare function shouldShowCreditRecovery(error: NormalizedCreditError, session: CreditPromptSession): boolean;
|
|
27
|
+
/** Full contextual screen (interactive TTY, first occurrence). */
|
|
28
|
+
export declare function renderCreditRecovery(authenticated: boolean): string;
|
|
29
|
+
/** Single-line hint (non-TTY, CI, or repeat occurrences in a session). */
|
|
30
|
+
export declare const CREDIT_RECOVERY_HINT = "Hint: ruflo auth login enables the free local Meta LLM proxy";
|
|
31
|
+
//# sourceMappingURL=credit-errors.d.ts.map
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Credit-error taxonomy and fail-closed classifier — ADR-303.
|
|
3
|
+
*
|
|
4
|
+
* Adapters map machine-readable provider codes into the canonical
|
|
5
|
+
* CreditErrorCode enum. Message text is NEVER parsed. Anything unmapped
|
|
6
|
+
* stays unclassified (confidence 0) and falls through to the ordinary
|
|
7
|
+
* error path. Only COGNITUM_CREDIT_EXHAUSTED — asserted by the Cognitum
|
|
8
|
+
* ledger, the single credit authority — may trigger the funnel surface.
|
|
9
|
+
*/
|
|
10
|
+
import { CreditErrorCode } from './types.js';
|
|
11
|
+
/**
|
|
12
|
+
* Provider-code → canonical-code table. Keys are exact machine-readable
|
|
13
|
+
* code strings from provider error payloads (mirrors the ADR-308 server
|
|
14
|
+
* taxonomy 1:1). Versioned by code review; unmapped codes land nowhere.
|
|
15
|
+
*/
|
|
16
|
+
const PROVIDER_CODE_TABLE = {
|
|
17
|
+
// Cognitum ledger (the single credit authority — ADR-303)
|
|
18
|
+
cognitum_credit_exhausted: { code: CreditErrorCode.COGNITUM_CREDIT_EXHAUSTED, retryable: false },
|
|
19
|
+
// Upstream provider quota/billing — NOT a Cognitum upsell moment
|
|
20
|
+
insufficient_quota: { code: CreditErrorCode.PROVIDER_QUOTA_EXHAUSTED, retryable: false },
|
|
21
|
+
billing_hard_limit_reached: { code: CreditErrorCode.PROVIDER_QUOTA_EXHAUSTED, retryable: false },
|
|
22
|
+
quota_exceeded: { code: CreditErrorCode.PROVIDER_QUOTA_EXHAUSTED, retryable: false },
|
|
23
|
+
// Retryable rate limits
|
|
24
|
+
rate_limit_exceeded: { code: CreditErrorCode.PROVIDER_RATE_LIMITED, retryable: true },
|
|
25
|
+
rate_limit_error: { code: CreditErrorCode.PROVIDER_RATE_LIMITED, retryable: true },
|
|
26
|
+
overloaded_error: { code: CreditErrorCode.SERVICE_UNAVAILABLE, retryable: true },
|
|
27
|
+
// Auth
|
|
28
|
+
authentication_error: { code: CreditErrorCode.AUTHENTICATION_FAILED, retryable: false },
|
|
29
|
+
invalid_api_key: { code: CreditErrorCode.AUTHENTICATION_FAILED, retryable: false },
|
|
30
|
+
permission_error: { code: CreditErrorCode.AUTHENTICATION_FAILED, retryable: false },
|
|
31
|
+
// Outages
|
|
32
|
+
api_error: { code: CreditErrorCode.SERVICE_UNAVAILABLE, retryable: true },
|
|
33
|
+
service_unavailable: { code: CreditErrorCode.SERVICE_UNAVAILABLE, retryable: true },
|
|
34
|
+
};
|
|
35
|
+
export function classifyCreditError(err) {
|
|
36
|
+
const key = err.providerCode?.trim().toLowerCase();
|
|
37
|
+
if (key && Object.prototype.hasOwnProperty.call(PROVIDER_CODE_TABLE, key)) {
|
|
38
|
+
const mapped = PROVIDER_CODE_TABLE[key];
|
|
39
|
+
return { code: mapped.code, confidence: 1, retryable: mapped.retryable, cause: err };
|
|
40
|
+
}
|
|
41
|
+
// Status-only signals are structural (401/403 → auth; 5xx → outage), but a
|
|
42
|
+
// bare 429 is ambiguous between rate-limit and quota — leave it unmapped.
|
|
43
|
+
if (err.status === 401 || err.status === 403) {
|
|
44
|
+
return { code: CreditErrorCode.AUTHENTICATION_FAILED, confidence: 1, retryable: false, cause: err };
|
|
45
|
+
}
|
|
46
|
+
if (err.status !== undefined && err.status >= 500) {
|
|
47
|
+
return { code: CreditErrorCode.SERVICE_UNAVAILABLE, confidence: 1, retryable: true, cause: err };
|
|
48
|
+
}
|
|
49
|
+
return { code: null, confidence: 0, retryable: false, cause: err };
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* The ADR-303 gate, fail-closed and frequency-capped: fires only for a
|
|
53
|
+
* confident, non-retryable Cognitum-ledger exhaustion, at most once per
|
|
54
|
+
* session. Everything else falls through to the ordinary error path.
|
|
55
|
+
*/
|
|
56
|
+
export function shouldShowCreditRecovery(error, session) {
|
|
57
|
+
return (error.code === CreditErrorCode.COGNITUM_CREDIT_EXHAUSTED &&
|
|
58
|
+
error.confidence === 1 &&
|
|
59
|
+
!error.retryable &&
|
|
60
|
+
!session.creditPromptShown);
|
|
61
|
+
}
|
|
62
|
+
/** Full contextual screen (interactive TTY, first occurrence). */
|
|
63
|
+
export function renderCreditRecovery(authenticated) {
|
|
64
|
+
const lines = [
|
|
65
|
+
'Daily hosted credits exhausted.',
|
|
66
|
+
'',
|
|
67
|
+
'Continue immediately by enabling',
|
|
68
|
+
'your free local Meta LLM Proxy.',
|
|
69
|
+
'',
|
|
70
|
+
'Benefits',
|
|
71
|
+
' ✓ Unlimited local requests',
|
|
72
|
+
' ✓ Automatic model routing',
|
|
73
|
+
' ✓ Lower latency',
|
|
74
|
+
' ✓ Privacy preserving',
|
|
75
|
+
' ✓ Cloud fallback',
|
|
76
|
+
'',
|
|
77
|
+
];
|
|
78
|
+
if (authenticated) {
|
|
79
|
+
lines.push('Start local proxy?', ' ruflo proxy enable');
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
lines.push('Sign in:', ' ruflo auth login');
|
|
83
|
+
}
|
|
84
|
+
return lines.join('\n');
|
|
85
|
+
}
|
|
86
|
+
/** Single-line hint (non-TTY, CI, or repeat occurrences in a session). */
|
|
87
|
+
export const CREDIT_RECOVERY_HINT = 'Hint: ruflo auth login enables the free local Meta LLM proxy';
|
|
88
|
+
//# sourceMappingURL=credit-errors.js.map
|