segreant 0.1.0 → 0.2.0
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/LICENSING.md +5 -3
- package/README.md +57 -19
- package/bin/segreant-statusline.mjs +20 -0
- package/bin/segreant.mjs +19 -8
- package/dist/alerts/detect.js +13 -0
- package/dist/billing/epistemic.js +4 -0
- package/dist/billing/importer.js +29 -10
- package/dist/billing/reconcile.js +122 -32
- package/dist/billing/scope.js +7 -0
- package/dist/budget/guard.js +33 -4
- package/dist/cli/allocCmd.js +73 -4
- package/dist/cli/askModelPrices.js +59 -0
- package/dist/cli/backupCmd.js +2 -1
- package/dist/cli/basisLines.js +45 -0
- package/dist/cli/billingCmd.js +68 -9
- package/dist/cli/capitalCmd.js +2 -1
- package/dist/cli/connectCmd.js +50 -9
- package/dist/cli/egressCmd.js +15 -3
- package/dist/cli/evidenceCmd.js +6 -3
- package/dist/cli/flags.js +68 -0
- package/dist/cli/homeCmd.js +117 -0
- package/dist/cli/importCmd.js +210 -54
- package/dist/cli/launchCmd.js +2 -1
- package/dist/cli/opsCmd.js +49 -2
- package/dist/cli/planCmd.js +181 -0
- package/dist/cli/pricingDeclareCmd.js +178 -0
- package/dist/cli/quotaCmd.js +190 -0
- package/dist/cli/runCmd.js +203 -51
- package/dist/cli/showCmd.js +37 -11
- package/dist/cli/teamCmd.js +5 -5
- package/dist/cli/ui.js +24 -8
- package/dist/cli/valueCmd.js +189 -14
- package/dist/cli.js +102 -21
- package/dist/config.js +95 -6
- package/dist/connect/antigravity.js +310 -0
- package/dist/connect/claudeCode.js +43 -3
- package/dist/connect/codex.js +38 -1
- package/dist/connect/importShared.js +58 -5
- package/dist/connect/opencode.js +37 -9
- package/dist/connect/protobuf.js +126 -0
- package/dist/connect/registry.js +46 -0
- package/dist/connect/repoRoot.js +112 -0
- package/dist/cost/basis.js +168 -0
- package/dist/cost/declarations.js +37 -0
- package/dist/cost/exactPricing.js +4 -1
- package/dist/cost/firstSight.js +98 -0
- package/dist/cost/overrides.js +230 -0
- package/dist/cost/pricing.js +228 -15
- package/dist/cost/reprice.js +130 -0
- package/dist/dashboard/claim-support.js +11 -8
- package/dist/dashboard/contracts.js +23 -3
- package/dist/dashboard/routes.js +980 -112
- package/dist/dashboard/sessions.js +345 -0
- package/dist/dashboard/settings.js +2 -1
- package/dist/dashboard/value-cache.js +105 -0
- package/dist/dashboard/web/app/components/claimInspector.js +6 -5
- package/dist/dashboard/web/app/components/drawer.js +105 -35
- package/dist/dashboard/web/app/components/ruleFields.js +144 -0
- package/dist/dashboard/web/app/components/spine.js +54 -24
- package/dist/dashboard/web/app/components/workflow.js +82 -0
- package/dist/dashboard/web/app/core/actions.js +734 -23
- package/dist/dashboard/web/app/core/api.js +37 -1
- package/dist/dashboard/web/app/core/chain.js +11 -4
- package/dist/dashboard/web/app/core/claimLayers.js +179 -51
- package/dist/dashboard/web/app/core/claimTypes.js +30 -0
- package/dist/dashboard/web/app/core/fmt.js +19 -2
- package/dist/dashboard/web/app/core/generated-contract.js +23 -3
- package/dist/dashboard/web/app/core/generated-payload-contract.js +1944 -465
- package/dist/dashboard/web/app/core/generated-types.js +1 -1
- package/dist/dashboard/web/app/core/griffin.js +10 -0
- package/dist/dashboard/web/app/core/jobs.js +12 -0
- package/dist/dashboard/web/app/core/period.js +35 -0
- package/dist/dashboard/web/app/core/periodWords.js +10 -0
- package/dist/dashboard/web/app/core/periods.js +32 -0
- package/dist/dashboard/web/app/core/pricingActions.js +264 -0
- package/dist/dashboard/web/app/core/privacyText.js +78 -0
- package/dist/dashboard/web/app/core/reconcileWords.js +63 -0
- package/dist/dashboard/web/app/core/registry.js +79 -46
- package/dist/dashboard/web/app/core/slug.js +9 -0
- package/dist/dashboard/web/app/core/theme.js +48 -0
- package/dist/dashboard/web/app/core/view.js +69 -0
- package/dist/dashboard/web/app/main.js +197 -15
- package/dist/dashboard/web/app/views/allocation.js +249 -61
- package/dist/dashboard/web/app/views/control.js +57 -16
- package/dist/dashboard/web/app/views/data.js +4 -4
- package/dist/dashboard/web/app/views/evidence.js +336 -87
- package/dist/dashboard/web/app/views/privacy.js +13 -0
- package/dist/dashboard/web/app/views/spend.js +30 -15
- package/dist/dashboard/web/app/views/statement.js +189 -0
- package/dist/dashboard/web/app/views/system.js +44 -4
- package/dist/dashboard/web/app/views/value.js +105 -59
- package/dist/dashboard/web/app/views/workbench.js +312 -0
- package/dist/dashboard/web/classic.html +16 -4
- package/dist/dashboard/web/index.html +1 -1
- package/dist/dashboard/web/styles/app.css +536 -23
- package/dist/demo/seed.js +153 -14
- package/dist/economics/ledger.js +1 -1
- package/dist/egress/policy.js +129 -5
- package/dist/export/allocationCsv.js +70 -0
- package/dist/export/billingCsv.js +6 -2
- package/dist/export/csv.js +10 -2
- package/dist/export/economic.js +6 -2
- package/dist/export/statementCsv.js +104 -0
- package/dist/git/correlate.js +86 -9
- package/dist/git/quality.js +107 -24
- package/dist/git/repoScope.js +207 -0
- package/dist/git/survivalStore.js +186 -0
- package/dist/guide.js +9 -4
- package/dist/plans/detect.js +147 -0
- package/dist/proxy/server.js +134 -14
- package/dist/proxy/usage.js +9 -4
- package/dist/quota/limits.js +138 -0
- package/dist/quota/statusline.js +68 -0
- package/dist/quota/view.js +122 -0
- package/dist/scan/knownApps.js +6 -2
- package/dist/scan/scan.js +9 -0
- package/dist/store/allocation.js +14 -0
- package/dist/store/db.js +259 -34
- package/dist/store/realization.js +78 -14
- package/dist/store/schema.js +59 -1
- package/dist/store/scopeEvidence.js +67 -0
- package/dist/util/pool.js +19 -0
- package/dist/util/time.js +7 -0
- package/dist/value/realization.js +321 -30
- package/dist/value/report.js +28 -8
- package/dist/value/selfReported.js +10 -9
- package/docs/ARCHITECTURE.md +8 -4
- package/docs/CAPABILITY-EVIDENCE-CONTRACT.md +33 -11
- package/docs/CLI-JSON-CONTRACT.md +38 -0
- package/docs/DATA-BOUNDARIES.md +66 -6
- package/docs/FAQ.md +43 -9
- package/docs/GETTING-STARTED.md +19 -12
- package/docs/GUIDE.md +81 -18
- package/docs/INTEGRATIONS.md +9 -9
- package/docs/METHODOLOGY.md +3 -2
- package/docs/NEUTRALITY.md +1 -1
- package/docs/PRIVACY.md +51 -0
- package/docs/README.md +1 -0
- package/docs/RELEASE-GATE.md +52 -7
- package/docs/THREAT-MODEL.md +2 -1
- package/package.json +3 -2
- package/pricing/models.json +1366 -60
package/dist/cli/valueCmd.js
CHANGED
|
@@ -11,7 +11,7 @@ import { loadConfig, mutateConfig, dbPath, isDemo } from '../config.js';
|
|
|
11
11
|
import { isGitRepo, projectName, resolveCommit } from '../git/correlate.js';
|
|
12
12
|
import { computeArtifactPersistence } from '../git/quality.js';
|
|
13
13
|
import { contributionEvidenceLines, summarizeContributionEvidence } from '../git/contribution.js';
|
|
14
|
-
import { loadRealization } from '../value/realization.js';
|
|
14
|
+
import { loadRealization, keptSummary, CLI_PERIOD_DAYS, CLI_GIT_BUDGET_MS } from '../value/realization.js';
|
|
15
15
|
import { WORK_WEEK_MINUTES } from '../value/timeReclaimed.js';
|
|
16
16
|
import { computeFrontier } from '../value/frontier.js';
|
|
17
17
|
// The value report's one composition — shared with the dashboard's '/api/value'.
|
|
@@ -25,6 +25,87 @@ import { describeDriftReading, driftReading } from '../value/drift.js';
|
|
|
25
25
|
import { C, color, usd, num, pct, gateGlyph, noteSource, printNotAGitRepo, printJson } from './ui.js';
|
|
26
26
|
import {} from './flags.js';
|
|
27
27
|
import { retentionNotice } from './retention.js';
|
|
28
|
+
/**
|
|
29
|
+
* With no request recorded there is no AI spend for git history to be measured
|
|
30
|
+
* against. The value commands used to walk every commit of the last 90 days
|
|
31
|
+
* anyway (54-87 s on a real repository) and then print 0% realized, an RoI of
|
|
32
|
+
* 0 and $0.00 per commit: a zero where the honest answer is "nothing to
|
|
33
|
+
* measure yet". Returns true when it has answered for the caller.
|
|
34
|
+
*/
|
|
35
|
+
async function answeredNothingSpent(store, flags, repo) {
|
|
36
|
+
if (store.earliestRequestMs() !== null)
|
|
37
|
+
return false;
|
|
38
|
+
// A path that is not a repository is the user's real problem, and the
|
|
39
|
+
// command's own check reports it with a failing status; answering "nothing
|
|
40
|
+
// spent" first hid that behind exit 0 (PR #131 review).
|
|
41
|
+
if (!(await isGitRepo(repo)))
|
|
42
|
+
return false;
|
|
43
|
+
// No surviving request is not "nothing spent" once retention has pruned
|
|
44
|
+
// rows or a realization snapshot is stored: the stored report is the answer
|
|
45
|
+
// then, and the command must reach it (review 4, F2).
|
|
46
|
+
if (store.retentionFloor().requestsPrunedBeforeMs !== null || store.countRealizationUnits() > 0)
|
|
47
|
+
return false;
|
|
48
|
+
const next = ['segreant scan --setup', 'segreant demo --serve'];
|
|
49
|
+
if (flags.json) {
|
|
50
|
+
printJson({ status: 'no_spend_recorded', measured: false, next });
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
const tty = process.stdout.isTTY ?? false;
|
|
54
|
+
console.log('');
|
|
55
|
+
console.log(' No AI spend is recorded yet, so there is nothing to measure against this repository.');
|
|
56
|
+
console.log(color(tty, C.gray, ` Import what your tools already logged: ${next[0]}`));
|
|
57
|
+
console.log(color(tty, C.gray, ` Or see every figure on sample data: ${next[1]}`));
|
|
58
|
+
console.log('');
|
|
59
|
+
}
|
|
60
|
+
store.close();
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* The budget decision in two sentences: whether the evidence shows the
|
|
65
|
+
* recommended cap doing better than the current one, and where the full record
|
|
66
|
+
* is. The record itself (renderBudgetCapDecision) stays one flag away.
|
|
67
|
+
*/
|
|
68
|
+
function plainCapVerdict(decision, canonical, soft) {
|
|
69
|
+
const label = (capUsd) => (capUsd === null || capUsd === undefined ? 'no cap' : usd(capUsd));
|
|
70
|
+
const recommendedCap = decision.actions.find((a) => a.action === 'apply_recommended')?.capUsd;
|
|
71
|
+
const currentCap = decision.actions.find((a) => a.action === 'keep_current')?.capUsd;
|
|
72
|
+
const recommended = label(recommendedCap);
|
|
73
|
+
const current = label(currentCap);
|
|
74
|
+
// Equal caps are one setting, not two options: there is nothing to compare,
|
|
75
|
+
// and blaming partly observed days for "no winner" would be a false reason
|
|
76
|
+
// (PR #142 review).
|
|
77
|
+
// The soft warning is part of the recommendation too: an equal daily cap
|
|
78
|
+
// with a different soft warning is a change, and is said as one (PR #145 review).
|
|
79
|
+
if (recommendedCap !== null && recommendedCap !== undefined && recommendedCap === currentCap) {
|
|
80
|
+
const first = soft.current === soft.recommended
|
|
81
|
+
? ` Your current ${current} cap and ${usd(soft.recommended)} soft warning already match the recommendation; nothing to change.`
|
|
82
|
+
: ` Your current ${current} daily cap already matches the recommendation. Only the soft warning differs: recommended ${usd(soft.recommended)}, yours is ${soft.current === null ? 'off' : usd(soft.current)}.`;
|
|
83
|
+
return [first, ' Full decision record: segreant budget --recommend --detail'];
|
|
84
|
+
}
|
|
85
|
+
const certificate = canonical.certificateBundle.dominance;
|
|
86
|
+
const currentPhrase = current === 'no cap' ? 'having no cap' : `your current ${current}`;
|
|
87
|
+
const verdict = certificate.status === 'proven_dominant'
|
|
88
|
+
? certificate.action === 'apply_recommended'
|
|
89
|
+
? ` On this evidence the recommended ${recommended} cap does better than ${currentPhrase}.`
|
|
90
|
+
: ` On this evidence ${currentPhrase} does better than the recommended ${recommended} cap.`
|
|
91
|
+
: ` Is ${recommended} better than ${currentPhrase}? Not shown: which days' spend was kept is only partly observed, so neither is proven better.`;
|
|
92
|
+
return [verdict, ' Full decision record, with each assumption and what would settle it: segreant budget --recommend --detail'];
|
|
93
|
+
}
|
|
94
|
+
/** The last line of the budget advice when nothing is applied: why, in the reader's terms. */
|
|
95
|
+
export function budgetClosingLine(applyPermitted, dominance, sameCap = null) {
|
|
96
|
+
if (sameCap?.softMatches)
|
|
97
|
+
return ' No change needed: your config already has this cap and soft warning.';
|
|
98
|
+
if (sameCap)
|
|
99
|
+
return ` Daily cap unchanged. To set only the soft warning: segreant budget --soft ${sameCap.softRecommended.toFixed(2)}`;
|
|
100
|
+
if (applyPermitted)
|
|
101
|
+
return ' Re-run with --apply to write these to your config.';
|
|
102
|
+
if (dominance?.status === 'proven_dominant' && dominance.action === 'apply_recommended') {
|
|
103
|
+
return ' No cap written: the evidence favours the new cap, but its inputs are not verified enough to change spend. --detail lists what is missing.';
|
|
104
|
+
}
|
|
105
|
+
if (dominance?.status === 'proven_dominant')
|
|
106
|
+
return ' No cap written: on this evidence your current setting does better.';
|
|
107
|
+
return ' No cap written: --apply needs evidence that the new cap does better, and this data does not show it. --detail lists what is missing.';
|
|
108
|
+
}
|
|
28
109
|
export async function cmdYield(flags) {
|
|
29
110
|
const repo = flags.repo ?? process.cwd();
|
|
30
111
|
const limit = flags.limit ? Number(flags.limit) : 30;
|
|
@@ -35,6 +116,8 @@ export async function cmdYield(flags) {
|
|
|
35
116
|
return;
|
|
36
117
|
}
|
|
37
118
|
const store = new Store(dbPath());
|
|
119
|
+
if (await answeredNothingSpent(store, flags, repo))
|
|
120
|
+
return;
|
|
38
121
|
const report = await computeArtifactPersistence(store, repo, { limit, windowDays, persist: true });
|
|
39
122
|
if (flags.json) {
|
|
40
123
|
printJson(report);
|
|
@@ -52,15 +135,25 @@ export async function cmdYield(flags) {
|
|
|
52
135
|
console.log(color(tty, C.gray, ' Recent commits below are provisional (retention still settling).'));
|
|
53
136
|
}
|
|
54
137
|
else {
|
|
55
|
-
const
|
|
138
|
+
const withheld = m.aiYield === null && m.spendWindowTruncatedCommits > 0;
|
|
139
|
+
const yieldStr = m.aiYield !== null
|
|
140
|
+
? `${m.aiYield.toFixed(1)} retained lines / $`
|
|
141
|
+
: withheld ? 'withheld (retention deleted the spend records)' : 'n/a (no AI cost attributed)';
|
|
56
142
|
console.log(` Retained lines / $ ${color(tty, C.green, yieldStr)}`);
|
|
57
143
|
console.log(` Effective spend ${m.effectiveSpendRatio === null ? '—' : color(tty, m.effectiveSpendRatio > 0.5 ? C.green : C.yellow, pct(m.effectiveSpendRatio))} ${color(tty, C.gray, 'of $ associated with retained artifacts')}`);
|
|
58
144
|
console.log(` Artifact retention ${color(tty, m.survivalRatio > 0.7 ? C.green : C.yellow, pct(m.survivalRatio))} ${color(tty, C.gray, `non-retained ${pct(m.churnRatio)}`)}`);
|
|
59
145
|
console.log(` Revert rate ${color(tty, m.revertRate < 0.05 ? C.green : C.red, pct(m.revertRate))}`);
|
|
60
|
-
|
|
146
|
+
const costScope = m.spendWindowTruncatedCommits > 0
|
|
147
|
+
? `${num(m.survivingLines)} retained lines · excludes ${m.spendWindowTruncatedCommits} commit(s) with pruned spend`
|
|
148
|
+
: `${num(m.survivingLines)} retained lines`;
|
|
149
|
+
console.log(` AI cost (matured) ${usd(m.totalCostUsd)} ${color(tty, C.gray, costScope)}`);
|
|
61
150
|
if (m.costPerSurvivingLine !== null) {
|
|
62
151
|
console.log(` Cost / retained line ${usd(m.costPerSurvivingLine)}`);
|
|
63
152
|
}
|
|
153
|
+
if (m.spendWindowTruncatedCommits > 0) {
|
|
154
|
+
console.log(color(tty, C.yellow, ` ● ${m.spendWindowTruncatedCommits} matured commit(s) lost spend records to retention inside their window.`));
|
|
155
|
+
console.log(color(tty, C.yellow, ' What remains understates what that work cost, so they are left out of AI cost and every per-dollar figure.'));
|
|
156
|
+
}
|
|
64
157
|
}
|
|
65
158
|
console.log('');
|
|
66
159
|
console.log(color(tty, C.bold, ' Per commit'));
|
|
@@ -73,10 +166,12 @@ export async function cmdYield(flags) {
|
|
|
73
166
|
const yld = c.aiYield === null ? '—' : c.aiYield.toFixed(0);
|
|
74
167
|
const status = c.reverted
|
|
75
168
|
? color(tty, C.red, 'REVERTED')
|
|
76
|
-
: c.
|
|
77
|
-
? color(tty, C.yellow, '
|
|
78
|
-
:
|
|
79
|
-
|
|
169
|
+
: c.spendWindowTruncated !== false
|
|
170
|
+
? color(tty, C.yellow, 'spend pruned')
|
|
171
|
+
: c.maturing
|
|
172
|
+
? color(tty, C.yellow, 'maturing')
|
|
173
|
+
: color(tty, C.green, 'matured');
|
|
174
|
+
console.log(` ${short} ${age.padStart(4)} ${(c.spendWindowTruncated !== false ? 'pruned' : usd(c.attributedCostUsd)).padStart(9)} ${String(c.linesAdded).padStart(6)} ${surv.padStart(9)} ${churn.padStart(5)} ${yld.padStart(5)} ${status}`);
|
|
80
175
|
}
|
|
81
176
|
console.log('');
|
|
82
177
|
console.log(color(tty, C.gray, ' Retained lines ÷ AI cost is an artifact-persistence lens, not code quality —'));
|
|
@@ -84,12 +179,75 @@ export async function cmdYield(flags) {
|
|
|
84
179
|
console.log('');
|
|
85
180
|
store.close();
|
|
86
181
|
}
|
|
182
|
+
/**
|
|
183
|
+
* The first answer a person wants, from git alone: of the AI work that became
|
|
184
|
+
* commits, how much is still in the code. Printed before the Standard, which
|
|
185
|
+
* needs more gates to call anything realized. Every figure is list cost.
|
|
186
|
+
*/
|
|
187
|
+
function printKeptAnswer(tty, report) {
|
|
188
|
+
const k = keptSummary(report);
|
|
189
|
+
const total = k.kept.units + k.notKept.units + k.contested.units + k.unknown.units + k.maturing.units;
|
|
190
|
+
if (total === 0)
|
|
191
|
+
return;
|
|
192
|
+
const days = report.windowDays;
|
|
193
|
+
const row = (label, b, what, tone) => {
|
|
194
|
+
if (b.units === 0)
|
|
195
|
+
return;
|
|
196
|
+
console.log(` ${color(tty, C.bold, label.padEnd(10))} ${color(tty, tone, usd(b.costUsd).padStart(10))} ${String(b.units).padStart(4)} commit${b.units === 1 ? ' ' : 's'} ${color(tty, C.gray, what)}`);
|
|
197
|
+
};
|
|
198
|
+
console.log('');
|
|
199
|
+
console.log(color(tty, C.bold, ' Did the AI work stay in the code?') + color(tty, C.gray, ' from git history · list cost (estimate), not your bill'));
|
|
200
|
+
row('Kept', k.kept, `most of its lines are still in the code after ${days} days, and no tracked check failed`, C.green);
|
|
201
|
+
row('Not kept', k.notKept, 'most of its lines were rewritten or removed, or it was reverted', C.yellow);
|
|
202
|
+
row('Disputed', k.contested, 'the checks disagreed, and nothing failed outright; not settled either way', C.gray);
|
|
203
|
+
row('Unknown', k.unknown, 'not measured in this run (the git time budget ran out)', C.gray);
|
|
204
|
+
const due = k.maturing.nextVerdictMs === null ? '' : `; first verdict ${new Date(k.maturing.nextVerdictMs).toISOString().slice(0, 10)}`;
|
|
205
|
+
row('Maturing', k.maturing, `younger than ${days} days${due}`, C.gray);
|
|
206
|
+
const c = report.periodCoverage;
|
|
207
|
+
if (c !== undefined) {
|
|
208
|
+
const days90 = Math.round((c.periodEndMs - c.periodStartMs) / 86_400_000);
|
|
209
|
+
const off = c.beforeOldestUsd + c.noCommitFollowedUsd + c.notCommittedYetUsd;
|
|
210
|
+
const share = c.scopedCostUsd > 0 ? Math.round((c.onCommitsUsd / c.scopedCostUsd) * 100) : 0;
|
|
211
|
+
console.log(color(tty, C.gray, ` ${share}% of this repository's ${usd(c.scopedCostUsd)} over the last ${days90} days is on a commit above.`));
|
|
212
|
+
if (off > 0.005) {
|
|
213
|
+
const parts = [];
|
|
214
|
+
if (c.noCommitFollowedUsd > 0.005)
|
|
215
|
+
parts.push(`${usd(c.noCommitFollowedUsd)} no commit followed within 8 hours`);
|
|
216
|
+
if (c.beforeOldestUsd > 0.005)
|
|
217
|
+
parts.push(`${usd(c.beforeOldestUsd)} before the oldest commit measured`);
|
|
218
|
+
if (c.notCommittedYetUsd > 0.005)
|
|
219
|
+
parts.push(`${usd(c.notCommittedYetUsd)} not committed yet`);
|
|
220
|
+
console.log(color(tty, C.gray, ` Not on a commit: ${parts.join(' · ')}.`));
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
const s = report.spendScope;
|
|
224
|
+
if (s !== undefined) {
|
|
225
|
+
const moved = s.linkedFolders.filter((f) => f.reason === 'moved').length;
|
|
226
|
+
const worktrees = s.linkedFolders.filter((f) => f.reason === 'worktree' || f.reason === 'clone').length;
|
|
227
|
+
const extra = [
|
|
228
|
+
`${s.linkedSessions} agent session${s.linkedSessions === 1 ? '' : 's'}`,
|
|
229
|
+
...(moved > 0 ? [`${moved} earlier location${moved === 1 ? '' : 's'} of this checkout`] : []),
|
|
230
|
+
...(worktrees > 0 ? [`${worktrees} other checkout${worktrees === 1 ? '' : 's'} (worktrees, clones)`] : []),
|
|
231
|
+
];
|
|
232
|
+
console.log(color(tty, C.gray, ` Includes ${extra.join(', ')}, linked by ${s.verifiedObservations} commits git confirmed they made.`));
|
|
233
|
+
}
|
|
234
|
+
}
|
|
87
235
|
export async function cmdRealize(flags) {
|
|
88
236
|
const repo = flags.repo ?? process.cwd();
|
|
89
|
-
const limit = flags.limit ? Number(flags.limit) :
|
|
237
|
+
const limit = flags.limit ? Number(flags.limit) : undefined;
|
|
90
238
|
const windowDays = flags.window ? Number(flags.window) : 14;
|
|
91
239
|
const store = new Store(dbPath());
|
|
92
|
-
|
|
240
|
+
if (await answeredNothingSpent(store, flags, repo))
|
|
241
|
+
return;
|
|
242
|
+
// Without --limit: every commit of the last 90 days, so the answer covers
|
|
243
|
+
// the same period the spend does.
|
|
244
|
+
const loaded = await loadRealization(store, repo, {
|
|
245
|
+
limit,
|
|
246
|
+
windowDays,
|
|
247
|
+
persist: true,
|
|
248
|
+
sinceDays: limit === undefined ? CLI_PERIOD_DAYS : undefined,
|
|
249
|
+
gitScanBudgetMs: CLI_GIT_BUDGET_MS,
|
|
250
|
+
});
|
|
93
251
|
if (!loaded) {
|
|
94
252
|
printNotAGitRepo(repo);
|
|
95
253
|
process.exitCode = 1;
|
|
@@ -105,6 +263,7 @@ export async function cmdRealize(flags) {
|
|
|
105
263
|
const tty = process.stdout.isTTY ?? false;
|
|
106
264
|
const m = report.matured;
|
|
107
265
|
const wiredGates = GATE_LADDER.filter((g) => m.instrumentation[g] > 0).length;
|
|
266
|
+
printKeptAnswer(tty, report);
|
|
108
267
|
console.log('');
|
|
109
268
|
console.log(color(tty, C.bold, ' The Realization Standard — did AI spend become real outcomes?'));
|
|
110
269
|
console.log(color(tty, C.gray, ` ${m.units} matured units (older than ${windowDays}d) · ${wiredGates} of ${GATE_LADDER.length} gates instrumented`));
|
|
@@ -339,6 +498,8 @@ export async function cmdRoi(flags) {
|
|
|
339
498
|
const windowDays = flags.window ? Number(flags.window) : 14;
|
|
340
499
|
const cfg = loadConfig();
|
|
341
500
|
const store = new Store(dbPath());
|
|
501
|
+
if (await answeredNothingSpent(store, flags, repo))
|
|
502
|
+
return;
|
|
342
503
|
// The whole composition — realization, this project's resolved baseline, the
|
|
343
504
|
// Lift source, the money inputs, RoI, the drift streams, sensitivity — is one
|
|
344
505
|
// sequence in src/value/report.ts, shared with `/api/value`. The three inputs
|
|
@@ -353,6 +514,8 @@ export async function cmdRoi(flags) {
|
|
|
353
514
|
repo,
|
|
354
515
|
windowDays,
|
|
355
516
|
persist: true,
|
|
517
|
+
sinceDays: CLI_PERIOD_DAYS,
|
|
518
|
+
gitScanBudgetMs: CLI_GIT_BUDGET_MS,
|
|
356
519
|
laborRatePerHour: flags['labor-rate'] !== undefined ? Number(flags['labor-rate']) : undefined,
|
|
357
520
|
tsfUpperBound: flags.tsf !== undefined ? Number(flags.tsf) : undefined,
|
|
358
521
|
riskAversion: flags['risk'] !== undefined ? Number(flags['risk']) : 0,
|
|
@@ -644,7 +807,13 @@ export async function cmdBudgetAdvisor(flags) {
|
|
|
644
807
|
console.log(color(tty, C.gray, ` · ${r}`));
|
|
645
808
|
if (decision) {
|
|
646
809
|
console.log('');
|
|
647
|
-
|
|
810
|
+
// The full record (certificate, assurance level, every unmet input claim)
|
|
811
|
+
// is for an auditor; printed by default it buried the answer under internal
|
|
812
|
+
// claim ids and read as contradicting the recommendation above it.
|
|
813
|
+
const lines = flags.detail
|
|
814
|
+
? renderBudgetCapDecision(decision, certificates, canonicalDecision)
|
|
815
|
+
: plainCapVerdict(decision, canonicalDecision, { current: cfg.budget.dailySoftUsd, recommended: softCap });
|
|
816
|
+
for (const line of lines)
|
|
648
817
|
console.log(color(tty, C.gray, line));
|
|
649
818
|
}
|
|
650
819
|
// Raw allocation and frontier trim/grow hints are withheld from this surface
|
|
@@ -676,9 +845,11 @@ export async function cmdBudgetAdvisor(flags) {
|
|
|
676
845
|
}
|
|
677
846
|
else {
|
|
678
847
|
console.log('');
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
848
|
+
// Three different reasons, said differently: the evidence picks no winner,
|
|
849
|
+
// it picks the current cap, or it picks the new cap but its inputs are not
|
|
850
|
+
// verified enough to change spend (PR #142 review).
|
|
851
|
+
const dominance = canonicalDecision?.certificateBundle.dominance;
|
|
852
|
+
console.log(color(tty, C.gray, budgetClosingLine(decision !== null && applyPermitted, dominance ?? null, cfg.budget.dailyUsd === dailyCap ? { softRecommended: softCap, softMatches: cfg.budget.dailySoftUsd === softCap } : null)));
|
|
682
853
|
}
|
|
683
854
|
console.log('');
|
|
684
855
|
store.close();
|
|
@@ -687,7 +858,11 @@ export async function cmdFrontier(flags) {
|
|
|
687
858
|
const repo = flags.repo ?? process.cwd();
|
|
688
859
|
const windowDays = flags.window ? Number(flags.window) : 14;
|
|
689
860
|
const store = new Store(dbPath());
|
|
690
|
-
|
|
861
|
+
if (await answeredNothingSpent(store, flags, repo))
|
|
862
|
+
return;
|
|
863
|
+
const loaded = await loadRealization(store, repo, {
|
|
864
|
+
windowDays, persist: true, sinceDays: CLI_PERIOD_DAYS, gitScanBudgetMs: CLI_GIT_BUDGET_MS,
|
|
865
|
+
});
|
|
691
866
|
if (!loaded) {
|
|
692
867
|
printNotAGitRepo(repo);
|
|
693
868
|
process.exitCode = 1;
|
package/dist/cli.js
CHANGED
|
@@ -10,9 +10,11 @@
|
|
|
10
10
|
* segreant prune prune old rows and compact the database
|
|
11
11
|
*/
|
|
12
12
|
import './util/quiet.js';
|
|
13
|
-
import {
|
|
13
|
+
import { join } from 'node:path';
|
|
14
|
+
import { demoDbPath, envOverrideKey, segreantHome } from './config.js';
|
|
14
15
|
import { packageVersion } from './version.js';
|
|
15
|
-
import {
|
|
16
|
+
import { configureSurvivalStore } from './git/survivalStore.js';
|
|
17
|
+
import { parseFlags, trackFlags, suggestOption, UserInputError } from './cli/flags.js';
|
|
16
18
|
// Command modules load on demand. Importing every command up front cost about
|
|
17
19
|
// two seconds on every invocation, `--help` included; a command now pays only
|
|
18
20
|
// for its own module graph. All specifiers are literal, so the import-closure
|
|
@@ -29,6 +31,9 @@ const causalCmd = () => import('./cli/causalCmd.js');
|
|
|
29
31
|
const capitalCmd = () => import('./cli/capitalCmd.js');
|
|
30
32
|
const opsCmd = () => import('./cli/opsCmd.js');
|
|
31
33
|
const showCmd = () => import('./cli/showCmd.js');
|
|
34
|
+
const planCmd = () => import('./cli/planCmd.js');
|
|
35
|
+
const homeCmd = () => import('./cli/homeCmd.js');
|
|
36
|
+
const quotaCmd = () => import('./cli/quotaCmd.js');
|
|
32
37
|
const runCmd = () => import('./cli/runCmd.js');
|
|
33
38
|
const launchCmd = () => import('./cli/launchCmd.js');
|
|
34
39
|
const backupCmd = () => import('./cli/backupCmd.js');
|
|
@@ -51,13 +56,17 @@ function cmdHelpShort() {
|
|
|
51
56
|
Usage: segreant <command> [options]
|
|
52
57
|
|
|
53
58
|
Get started
|
|
54
|
-
|
|
59
|
+
(no command) This month, your plans, what stayed in the code, and the next step
|
|
60
|
+
guide Where you are and the single next step
|
|
55
61
|
scan Find the AI tools and git repos on this machine and preview
|
|
56
62
|
a setup plan; --setup imports and correlates them
|
|
57
63
|
demo --serve Every screen on clearly-labeled synthetic data, no API key
|
|
58
64
|
|
|
59
65
|
Every day
|
|
60
66
|
today | week | month Spend for a window (--json)
|
|
67
|
+
plan Your plans, what you pay, and the work they did (set <tool> <usd>)
|
|
68
|
+
quota The vendors' usage meters, pace to reset, and Segreant's count (--json)
|
|
69
|
+
statusline Claude Code status line: records its live usage meter (--setup shows how)
|
|
61
70
|
start Start the proxy and the local dashboard
|
|
62
71
|
launch -- <command> Run a tool metered through the proxy while it runs
|
|
63
72
|
budget Set caps: --daily N --soft N --session N --runaway N
|
|
@@ -76,15 +85,18 @@ function cmdHelp() {
|
|
|
76
85
|
Usage: segreant <command> [options]
|
|
77
86
|
|
|
78
87
|
Commands
|
|
88
|
+
(no command) Bare "segreant": this month, your plans, how much of the
|
|
89
|
+
AI work stayed in the code, and the next step
|
|
79
90
|
guide Where you are + the single next step, read from your
|
|
80
|
-
actual state —
|
|
91
|
+
actual state — the full checklist (--json)
|
|
81
92
|
start Start the proxy + local dashboard
|
|
82
93
|
egress Inspect/verify Segreant-process egress; plan exact cloud
|
|
83
94
|
rules without mutation, then persist only with:
|
|
84
95
|
egress apply --apply --mode controlled_cloud
|
|
85
96
|
(--id, --purpose, --data-class, --method, --origin,
|
|
86
|
-
--path-prefix).
|
|
87
|
-
|
|
97
|
+
--path-prefix). Built in: loopback, the public price
|
|
98
|
+
list and the official OpenAI/Anthropic APIs. This is
|
|
99
|
+
not a machine-wide firewall.
|
|
88
100
|
causal Retained randomized-study evidence: status, inspect,
|
|
89
101
|
replay verification, and review-only OPE from the
|
|
90
102
|
Store-owned action log. V1 is inspect-only; all causal
|
|
@@ -101,6 +113,10 @@ function cmdHelp() {
|
|
|
101
113
|
commitment, consumption, showback, opportunity and
|
|
102
114
|
policy-relative fairness; no chargeback or action.
|
|
103
115
|
today | week | month Show spend for a window (--json)
|
|
116
|
+
plan Plans, prices you set, and the list-price work they did
|
|
117
|
+
quota Vendor usage meters and the pace to their reset
|
|
118
|
+
statusline Claude Code status line: records its live usage meter
|
|
119
|
+
on every refresh (--setup prints the line to add)
|
|
104
120
|
economic Inspect exact economic events, roles, bases, and legacy coverage
|
|
105
121
|
(--days N | --all, --target-currency UNIT, --as-of <ISO>,
|
|
106
122
|
--effective-at <ISO>, --json). Period controls use canonical
|
|
@@ -116,11 +132,18 @@ function cmdHelp() {
|
|
|
116
132
|
recipe). No tool lists the connectors.
|
|
117
133
|
import <tool|all> NATIVE metering, no routing: read the usage a tool
|
|
118
134
|
already logs locally — works on subscriptions the proxy
|
|
119
|
-
can never see. Tools: claude-code, opencode, codex
|
|
120
|
-
all). Idempotent. --watch keeps it live (poll every N
|
|
135
|
+
can never see. Tools: claude-code, opencode, codex,
|
|
136
|
+
antigravity (or all). Idempotent. --watch keeps it live (poll every N
|
|
121
137
|
sec: --every N). Files unchanged since their last full
|
|
122
138
|
import are skipped; --rescan reads them again.
|
|
123
139
|
(--root <dir>, --days N, --json)
|
|
140
|
+
alloc <action> Cost-centre allocation: who has DECIDED owns the money.
|
|
141
|
+
alloc centre <id> --name N [--owner O]; alloc rule <id>
|
|
142
|
+
--method direct|fixed_split|proportional_to_direct
|
|
143
|
+
--centre C --match-project P; alloc run --from <date>
|
|
144
|
+
--to <date> previews a split, --apply records it (--json).
|
|
145
|
+
alloc centres | rules | runs list them; alloc revoke <id>
|
|
146
|
+
closes a rule; alloc export <run-id> --out <csv>.
|
|
124
147
|
billing <action> Import, inspect, or export LOCAL operator-supplied
|
|
125
148
|
provider billing evidence. V1 accepts a strict OpenAI
|
|
126
149
|
evidence JSON only; it never overwrites metered estimates
|
|
@@ -199,7 +222,7 @@ function cmdHelp() {
|
|
|
199
222
|
--include-imported on|off: whether imported subscription
|
|
200
223
|
spend counts toward cap ENFORCEMENT (default off — the
|
|
201
224
|
cap governs live proxy traffic, the spend it can block)
|
|
202
|
-
budget --recommend Suggest a value-aware budget from usage + realized value
|
|
225
|
+
budget --recommend Suggest a value-aware budget from usage + realized value (--detail: full decision record)
|
|
203
226
|
(--repo <path> for value-based, --apply to write, --json)
|
|
204
227
|
budget --control Evaluate one bounded autonomous daily-cap control step
|
|
205
228
|
from a versioned JSON policy (--policy <file>, --repo <path>,
|
|
@@ -226,6 +249,15 @@ function cmdHelp() {
|
|
|
226
249
|
when stale; --auto off to disable)
|
|
227
250
|
Evidence: pricing --coverage [--days N|--all] (--json;
|
|
228
251
|
read-only historical card/match cohorts)
|
|
252
|
+
Declare: pricing set <model> --input <usd> --output <usd>
|
|
253
|
+
[--cache-read <usd>] [--cache-write <usd>]
|
|
254
|
+
[--cache-write-1h <usd>] (USD per 1M tokens)
|
|
255
|
+
Or: pricing set <model> --like <known-model>
|
|
256
|
+
Or: pricing set <model> --free (user-declared local price)
|
|
257
|
+
Remove: pricing unset <model>
|
|
258
|
+
Still need a price: pricing unknown [--json]
|
|
259
|
+
Declarations leave recorded cost unchanged; segreant reprice
|
|
260
|
+
previews changes, --apply writes them.
|
|
229
261
|
reprice Re-cost rows that were priced with a fallback estimate,
|
|
230
262
|
using the current rate card — only rows the card now
|
|
231
263
|
resolves EXACTLY are rewritten; remaining estimates are
|
|
@@ -268,13 +300,11 @@ function cmdHelp() {
|
|
|
268
300
|
--version Print the Segreant version
|
|
269
301
|
|
|
270
302
|
Setup
|
|
271
|
-
1)
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
segreant egress apply --apply ...
|
|
277
|
-
(Skip this for a local loopback-only model.)
|
|
303
|
+
1) OpenAI and Anthropic work with no setup. Another provider (OpenRouter, a
|
|
304
|
+
gateway) needs one rule; a refused request prints the exact command:
|
|
305
|
+
segreant egress apply --apply --mode controlled_cloud --id <as printed>
|
|
306
|
+
--purpose provider_inference --data-class provider_request --method POST
|
|
307
|
+
--origin https://<host> --path-prefix /
|
|
278
308
|
2) segreant start
|
|
279
309
|
3) $env:ANTHROPIC_BASE_URL="http://localhost:8090" (PowerShell)
|
|
280
310
|
$env:OPENAI_BASE_URL="http://localhost:8090/v1"
|
|
@@ -287,14 +317,30 @@ function cmdHelp() {
|
|
|
287
317
|
}
|
|
288
318
|
async function main() {
|
|
289
319
|
const argv = process.argv.slice(2);
|
|
290
|
-
//
|
|
291
|
-
//
|
|
292
|
-
|
|
320
|
+
// Survival measurements are kept between runs in the home directory, so a
|
|
321
|
+
// later value run spends its git budget on what an earlier one did not reach.
|
|
322
|
+
// Only the CLI (and the dashboard it starts) turns this on.
|
|
323
|
+
configureSurvivalStore(join(segreantHome(), 'survival'));
|
|
324
|
+
// Bare `segreant` opens the home view, not the reference: what was spent this
|
|
325
|
+
// month, what stayed in the code, and the next step (or, on a new machine, the
|
|
326
|
+
// one command that reads the logs). `guide` keeps the full checklist.
|
|
327
|
+
// Flags with no command (`segreant --demo`) are flags for the home view; the
|
|
328
|
+
// help text says --demo works on any read command, and bare `segreant` is one.
|
|
329
|
+
// --help/-h and --version/-v stay commands of their own.
|
|
330
|
+
// Only long options qualify: parseFlags reads `--name` alone, so a single-dash
|
|
331
|
+
// token here would be swallowed as a positional and `segreant -bogus` would
|
|
332
|
+
// render the home view and exit 0 (PR #142 review).
|
|
333
|
+
const flagsOnly = argv[0] !== undefined && argv[0].startsWith('--')
|
|
334
|
+
&& !['--help', '--version', '--'].includes(argv[0]);
|
|
335
|
+
const cmd = flagsOnly ? 'home' : argv[0] ?? 'home';
|
|
336
|
+
const rest = flagsOnly ? argv : argv.slice(1);
|
|
293
337
|
// `exec` wraps another command: everything after the bare `--` belongs to the
|
|
294
338
|
// wrapped command verbatim and must never be flag-parsed.
|
|
295
339
|
const sep = argv.indexOf('--');
|
|
296
340
|
const wraps = cmd === 'exec' || cmd === 'launch';
|
|
297
|
-
const
|
|
341
|
+
const parsed = parseFlags(wraps && sep !== -1 ? argv.slice(1, sep) : rest);
|
|
342
|
+
const tracked = trackFlags(parsed);
|
|
343
|
+
const flags = tracked.flags;
|
|
298
344
|
const wrapped = wraps && sep !== -1 ? argv.slice(sep + 1) : [];
|
|
299
345
|
// Demo mode: point every store-open at an isolated demo.db and flag surfaces
|
|
300
346
|
// to render the DEMO label. One switch covers the CLI and the in-process
|
|
@@ -331,6 +377,17 @@ async function main() {
|
|
|
331
377
|
case 'month':
|
|
332
378
|
(await showCmd()).cmdShow('month', flags);
|
|
333
379
|
break;
|
|
380
|
+
case 'plan':
|
|
381
|
+
case 'plans':
|
|
382
|
+
(await planCmd()).cmdPlan(flags);
|
|
383
|
+
break;
|
|
384
|
+
case 'quota':
|
|
385
|
+
case 'limits':
|
|
386
|
+
(await quotaCmd()).cmdQuota(flags);
|
|
387
|
+
break;
|
|
388
|
+
case 'statusline':
|
|
389
|
+
(await quotaCmd()).cmdStatusline(flags);
|
|
390
|
+
break;
|
|
334
391
|
case 'economic':
|
|
335
392
|
case 'economics':
|
|
336
393
|
(await economicCmd()).cmdEconomic(flags);
|
|
@@ -377,12 +434,15 @@ async function main() {
|
|
|
377
434
|
case 'export':
|
|
378
435
|
(await showCmd()).cmdExport(flags);
|
|
379
436
|
break;
|
|
437
|
+
case 'home':
|
|
438
|
+
(await homeCmd()).cmdHome(flags);
|
|
439
|
+
break;
|
|
380
440
|
case 'guide':
|
|
381
441
|
case 'next':
|
|
382
442
|
await (await opsCmd()).cmdGuide(flags);
|
|
383
443
|
break;
|
|
384
444
|
case 'doctor':
|
|
385
|
-
await (await opsCmd()).cmdDoctor();
|
|
445
|
+
await (await opsCmd()).cmdDoctor(flags);
|
|
386
446
|
break;
|
|
387
447
|
case 'audit':
|
|
388
448
|
await (await opsCmd()).cmdAudit(flags);
|
|
@@ -506,6 +566,27 @@ async function main() {
|
|
|
506
566
|
default:
|
|
507
567
|
console.error(` Unknown command: ${cmd}\n Run "segreant help" for usage.`);
|
|
508
568
|
process.exitCode = 1;
|
|
569
|
+
return;
|
|
570
|
+
}
|
|
571
|
+
// Long-running commands keep reading options after this point, so only
|
|
572
|
+
// commands that have finished are checked.
|
|
573
|
+
// A command that already failed has said why; its exit code stays.
|
|
574
|
+
// --watch and --serve count only when the command itself read them: read
|
|
575
|
+
// here through the tracking proxy they would mark themselves used, and
|
|
576
|
+
// `segreant --watch` would render the home view and exit 0 (PR #142 review).
|
|
577
|
+
const consumed = (key) => parsed[key] === true && !tracked.unread().includes(key);
|
|
578
|
+
const longRunning = cmd === 'start' || wraps || consumed('watch') || consumed('serve');
|
|
579
|
+
const failed = typeof process.exitCode === 'number' && process.exitCode !== 0;
|
|
580
|
+
if (!longRunning && !failed) {
|
|
581
|
+
const unread = tracked.unread().filter((k) => k !== 'debug');
|
|
582
|
+
for (const k of unread) {
|
|
583
|
+
const near = suggestOption(k);
|
|
584
|
+
console.error(near === k
|
|
585
|
+
? ` --${k} does nothing for "segreant ${cmd}"; nothing was done with it.`
|
|
586
|
+
: ` Unknown option --${k} for "segreant ${cmd}"; it did nothing.${near ? ` Did you mean --${near}?` : ''}`);
|
|
587
|
+
}
|
|
588
|
+
if (unread.length > 0)
|
|
589
|
+
process.exitCode = 2;
|
|
509
590
|
}
|
|
510
591
|
}
|
|
511
592
|
// A reader closing early (e.g. `segreant scan | head`) makes further console.log
|