@cspeach/cli 1.1.32 → 1.1.34
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/dist/agent/anthropic-provider.js +2 -0
- package/dist/agent/azure-profile.js +7 -0
- package/dist/agent/connection-profile.js +49 -0
- package/dist/agent/loop.js +61 -8
- package/dist/agent/provider-shape.js +36 -0
- package/dist/agent/providers/ai-hub-provider.js +6 -5
- package/dist/agent/providers/azure-provider.js +125 -0
- package/dist/agent/providers/byok-provider.js +8 -7
- package/dist/agent/providers/factory.js +8 -3
- package/dist/agent/providers/local-provider.js +18 -22
- package/dist/agent/risk-gate.js +202 -0
- package/dist/agent/summarise-via-provider.js +55 -8
- package/dist/agent/tool-dispatch.js +19 -0
- package/dist/agent/turn-error-ux.js +22 -0
- package/dist/auth/api-key.js +10 -5
- package/dist/auth/auth-file.js +35 -6
- package/dist/auth/azure/azure-key.js +21 -0
- package/dist/auth/azure/azure-log.js +45 -0
- package/dist/auth/azure/cache-store.js +171 -0
- package/dist/auth/azure/clear.js +28 -0
- package/dist/auth/azure/loopback.js +192 -0
- package/dist/auth/azure/msal-client.js +41 -0
- package/dist/auth/azure/sign-in.js +219 -0
- package/dist/auth/azure/startup-sign-in.js +64 -0
- package/dist/auth/azure/token-provider.js +116 -0
- package/dist/auth/key-resolver.js +258 -0
- package/dist/auth/me.js +11 -5
- package/dist/cli.js +21 -2
- package/dist/commands/compact.js +12 -1
- package/dist/commands/config-set.js +74 -3
- package/dist/commands/config-show.js +25 -0
- package/dist/commands/cost.js +25 -8
- package/dist/commands/help.js +2 -1
- package/dist/commands/login-azure.js +33 -0
- package/dist/commands/login.js +56 -8
- package/dist/commands/logout.js +34 -10
- package/dist/commands/plan-boundary.js +4 -4
- package/dist/commands/plan-model-tier.js +8 -3
- package/dist/commands/usage.js +112 -0
- package/dist/commands/whoami.js +113 -9
- package/dist/config/loader.js +5 -0
- package/dist/connection/azure-auth.js +135 -0
- package/dist/connection/azure-errors.js +103 -0
- package/dist/connection/azure-facts.js +21 -0
- package/dist/connection/azure-gate.js +40 -0
- package/dist/connection/azure-last-call.js +5 -0
- package/dist/connection/azure-turn-error.js +25 -0
- package/dist/connection/cache.js +70 -0
- package/dist/connection/fetch.js +33 -0
- package/dist/connection/guard.js +94 -0
- package/dist/connection/limit-gate.js +26 -0
- package/dist/connection/local-usage.js +71 -0
- package/dist/connection/login-step.js +62 -0
- package/dist/connection/startup.js +67 -0
- package/dist/connection/state.js +68 -0
- package/dist/connection/types.js +1 -0
- package/dist/cost/cost-log.js +3 -1
- package/dist/cost/credits-wire.js +1 -1
- package/dist/cost/credits.js +4 -0
- package/dist/cost/display-mode.js +7 -0
- package/dist/cost/month-usage.js +45 -0
- package/dist/cost/router-spend.js +3 -0
- package/dist/cost/session-cost.js +4 -0
- package/dist/doctor/checks/azure.js +100 -0
- package/dist/doctor/checks/credits.js +4 -3
- package/dist/doctor/checks/usage-reporting.js +22 -0
- package/dist/doctor/run.js +7 -0
- package/dist/licence/licence-check.js +205 -0
- package/dist/licence/paths.js +6 -0
- package/dist/licence/turn-gate.js +66 -0
- package/dist/models/model-identity.js +65 -0
- package/dist/models/resolve.js +8 -0
- package/dist/models/server-config.js +3 -2
- package/dist/one-shot.js +86 -2
- package/dist/renderer/footer-line.js +7 -1
- package/dist/renderer/status-footer.js +13 -6
- package/dist/repl/ask-or-no.js +18 -0
- package/dist/repl/builtin-commands.js +12 -1
- package/dist/repl/post-turn-status.js +3 -2
- package/dist/repl/safety-confirm.js +47 -2
- package/dist/repl/session-spend-line.js +4 -1
- package/dist/repl/slash-completer.js +1 -0
- package/dist/repl/startup-account.js +51 -0
- package/dist/repl.js +223 -48
- package/dist/router/byok-classifier.js +41 -14
- package/dist/router/classifier.js +5 -16
- package/dist/router/router-target.js +41 -0
- package/dist/sap/onboarding.js +70 -64
- package/dist/session/audit-export.js +11 -4
- package/dist/skills/manifest-client.js +37 -4
- package/dist/skills/skills-cache.js +115 -0
- package/dist/skills/source-authed.js +276 -0
- package/dist/tools/filesystem/glob.js +12 -3
- package/dist/tools/filesystem/grep.js +14 -3
- package/dist/tools/tool-loading.js +9 -10
- package/dist/usage/usage-queue.js +323 -0
- package/dist/usage/usage-reporter.js +270 -0
- package/package.json +3 -2
- package/dist/skills/bundled-skills.js +0 -286
- package/dist/skills/source-bundled.js +0 -20
- package/dist/skills/source-manifest.js +0 -36
|
@@ -8,19 +8,30 @@
|
|
|
8
8
|
* read of the process-wide cost display mode (cost/credits.ts); no
|
|
9
9
|
* inquirer, so the router can import it.
|
|
10
10
|
*/
|
|
11
|
-
import { creditsActive } from '../cost/credits.js';
|
|
11
|
+
import { creditsActive, tokensOnlyActive } from '../cost/credits.js';
|
|
12
12
|
// Pure generated data (type-only import inside), so this file stays light.
|
|
13
13
|
import { BUNDLED_RUNS } from '../runs/bundled-runs.js';
|
|
14
14
|
export const COST_DESCRIPTION_USD = 'Show this session\'s spend so far ($ and tokens)';
|
|
15
15
|
export const COST_DESCRIPTION_CREDITS = 'Show the credits this session has used so far';
|
|
16
|
+
/** Azure tenant mode (Task 7, ruling 5): tokens only — never "$" or credits. */
|
|
17
|
+
export const COST_DESCRIPTION_TOKENS = "Show this session's tokens so far";
|
|
16
18
|
/** Piece 2 §10: managed customers see credits, never "$" (cost/display-mode.ts). */
|
|
19
|
+
/** /usage (2026-10-09): this month in credits — see commands/usage.ts. */
|
|
20
|
+
export const USAGE_DESCRIPTION = 'Show this month’s credits, your balance and the top skills';
|
|
21
|
+
export const USAGE_DESCRIPTION_TOKENS = "Show where your usage is: your company's Azure invoice";
|
|
17
22
|
export function costCommandDescription() {
|
|
23
|
+
if (tokensOnlyActive())
|
|
24
|
+
return COST_DESCRIPTION_TOKENS;
|
|
18
25
|
return creditsActive() ? COST_DESCRIPTION_CREDITS : COST_DESCRIPTION_USD;
|
|
19
26
|
}
|
|
27
|
+
export function usageCommandDescription() {
|
|
28
|
+
return tokensOnlyActive() ? USAGE_DESCRIPTION_TOKENS : USAGE_DESCRIPTION;
|
|
29
|
+
}
|
|
20
30
|
export const BUILTIN_ENTRIES = [
|
|
21
31
|
// Piece 2: /cancel is not listed — it exists only in the classic loop
|
|
22
32
|
// (repl.tsx), and Esc / Ctrl+C already cancel under Ink.
|
|
23
33
|
{ name: '/cost', get description() { return costCommandDescription(); } },
|
|
34
|
+
{ name: '/usage', get description() { return usageCommandDescription(); } },
|
|
24
35
|
{ name: '/compact', description: 'Shrink a long session and keep the facts (later turns cost far less)' },
|
|
25
36
|
{ name: '/export', description: 'Export the signable session audit record — /export audit [filename]' },
|
|
26
37
|
{ name: '/context', description: 'Show what fills the model context window — a composition grid of bars' },
|
|
@@ -21,7 +21,7 @@ import { computeCost } from '../cost/pricing.js';
|
|
|
21
21
|
import { computeSessionCost } from '../cost/session-cost.js';
|
|
22
22
|
import { globalStore } from '../ui/sap-state-store.js';
|
|
23
23
|
import { turnStatusEmitter } from '../ui/turn-status-emitter.js';
|
|
24
|
-
import { globalCredits } from '../cost/credits.js';
|
|
24
|
+
import { globalCredits, tokensOnlyActive } from '../cost/credits.js';
|
|
25
25
|
import { printStatusFooter, formatStatusFooter } from '../renderer/status-footer.js';
|
|
26
26
|
import { shouldUseInk } from '../renderer/tty.js';
|
|
27
27
|
import { isUserPrompt } from '../session/user-prompt.js';
|
|
@@ -49,7 +49,8 @@ export function computeTurnFacts(session, snapshot, turnModel) {
|
|
|
49
49
|
const userTurns = session.messages.filter(isUserPrompt).length;
|
|
50
50
|
const lifetimeAfter = session.usage.input_tokens + session.usage.output_tokens;
|
|
51
51
|
const turnTokens = Math.max(0, lifetimeAfter - (snapshot.input + snapshot.output));
|
|
52
|
-
|
|
52
|
+
// Azure: no price for the customer's deployment — never consult one.
|
|
53
|
+
const turnCost = tokensOnlyActive() ? 0 : computeCost(turnModel ?? session.model, {
|
|
53
54
|
input: session.usage.input_tokens - snapshot.input,
|
|
54
55
|
output: session.usage.output_tokens - snapshot.output,
|
|
55
56
|
cacheRead: session.usage.cache_read_input_tokens - snapshot.cacheRead,
|
|
@@ -29,6 +29,8 @@ const RULE_LABEL = {
|
|
|
29
29
|
RULE_9_TRANSPORT_ISOLATION: 'RULE 9 — transport isolation',
|
|
30
30
|
RULE_10_VERIFY_AFTER_WRITE: 'RULE 10 — verify after write',
|
|
31
31
|
CREDENTIAL_HANDOVER: 'SAP login handed to a local process',
|
|
32
|
+
RISKY_COMMAND: 'risky command (known pattern)',
|
|
33
|
+
CREDENTIAL_FILE_READ: 'credential file read',
|
|
32
34
|
};
|
|
33
35
|
/** Piece 2 §5 — plain-words heading: what could go wrong. No rule codes, no tool names. */
|
|
34
36
|
export const SAFETY_HEADING = {
|
|
@@ -39,6 +41,8 @@ export const SAFETY_HEADING = {
|
|
|
39
41
|
RULE_9_TRANSPORT_ISOLATION: 'This change may land in a transport shared with other work',
|
|
40
42
|
RULE_10_VERIFY_AFTER_WRITE: 'This change is not proven until it is checked',
|
|
41
43
|
CREDENTIAL_HANDOVER: 'Your SAP login will be handed to a local program',
|
|
44
|
+
RISKY_COMMAND: 'This command matches a known risky pattern',
|
|
45
|
+
CREDENTIAL_FILE_READ: 'This reads a file that may hold passwords or keys',
|
|
42
46
|
};
|
|
43
47
|
/** Bottom-edge note: which rule is asking. */
|
|
44
48
|
export const SAFETY_FOOTNOTE = {
|
|
@@ -49,6 +53,8 @@ export const SAFETY_FOOTNOTE = {
|
|
|
49
53
|
RULE_9_TRANSPORT_ISOLATION: 'Forge Rule 9',
|
|
50
54
|
RULE_10_VERIFY_AFTER_WRITE: 'Forge Rule 10',
|
|
51
55
|
CREDENTIAL_HANDOVER: 'Credential handover',
|
|
56
|
+
RISKY_COMMAND: 'Risky command check',
|
|
57
|
+
CREDENTIAL_FILE_READ: 'Credential file check',
|
|
52
58
|
};
|
|
53
59
|
/** Rule 7 covers both overwrite and delete; the delete says so. */
|
|
54
60
|
export function safetyHeading(a) {
|
|
@@ -103,8 +109,40 @@ function whatLabel(a) {
|
|
|
103
109
|
return fallback;
|
|
104
110
|
return toolActivityLabel(tool, args);
|
|
105
111
|
}
|
|
112
|
+
/** The risk gate's two rules — their subject is a model-written command or path. */
|
|
113
|
+
function isRiskRule(a) {
|
|
114
|
+
return a.rule === 'RISKY_COMMAND' || a.rule === 'CREDENTIAL_FILE_READ';
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Fix round 1 (F9) — the most of a risky command or path the card shows. A
|
|
118
|
+
* longer one shows its head and its tail with the hidden length between them
|
|
119
|
+
* (fix round 2, L2: a command padded past the cap cannot hide its target on
|
|
120
|
+
* the classic card, which has no scrollback copy). Ink also writes the whole
|
|
121
|
+
* text to scrollback first, like the Rule 8 summary. Never redacted.
|
|
122
|
+
*/
|
|
123
|
+
export const RISK_SUBJECT_MAX = 300;
|
|
124
|
+
const RISK_SUBJECT_HEAD = 200;
|
|
125
|
+
const RISK_SUBJECT_TAIL = 80;
|
|
126
|
+
/** The subject as shown on a card: display-cleaned (F8), cut to head + tail past RISK_SUBJECT_MAX (F9, L2). */
|
|
127
|
+
function riskSubject(name) {
|
|
128
|
+
const c = clean(name);
|
|
129
|
+
if (c.length <= RISK_SUBJECT_MAX)
|
|
130
|
+
return c;
|
|
131
|
+
const hidden = c.length - RISK_SUBJECT_HEAD - RISK_SUBJECT_TAIL;
|
|
132
|
+
return `${c.slice(0, RISK_SUBJECT_HEAD)} ${getGlyphs().more} (+${hidden} chars) ${c.slice(-RISK_SUBJECT_TAIL)}`;
|
|
133
|
+
}
|
|
106
134
|
/** What / Where / Undo rows (plain text; the card paints them dim). */
|
|
107
135
|
export function safetyFacts(a) {
|
|
136
|
+
// Security batch item 4 — the risk gate's cards: the exact command or path,
|
|
137
|
+
// why it was flagged, and that it runs on this computer.
|
|
138
|
+
if (isRiskRule(a)) {
|
|
139
|
+
return [
|
|
140
|
+
`What ${a.rule === 'RISKY_COMMAND' ? 'Run' : 'Read'} ${riskSubject(a.object.name)}`,
|
|
141
|
+
...boundedWhatLines(whatLines(a)).map((t) => ` ${t}`),
|
|
142
|
+
'Where this computer',
|
|
143
|
+
`Undo ${a.rule === 'RISKY_COMMAND' ? 'Cannot be undone' : 'Once read, its text goes to the model'}`,
|
|
144
|
+
];
|
|
145
|
+
}
|
|
108
146
|
const what = a.rule === 'CREDENTIAL_HANDOVER'
|
|
109
147
|
? `Hand the SAP login for ${clean(a.object.name)} to a local program`
|
|
110
148
|
: whatLabel(a);
|
|
@@ -162,6 +200,10 @@ async function confirmOnCards(action) {
|
|
|
162
200
|
// The card keeps the header and the last lines; the whole text lands above it.
|
|
163
201
|
emitTrace(what.join('\n'));
|
|
164
202
|
}
|
|
203
|
+
if (isRiskRule(action) && clean(action.object.name).length > RISK_SUBJECT_MAX) {
|
|
204
|
+
// F9 — the card shows the cut; the whole (cleaned) command lands above it.
|
|
205
|
+
emitTrace(clean(action.object.name));
|
|
206
|
+
}
|
|
165
207
|
const first = await askConfirm({
|
|
166
208
|
id: 'safety-confirm',
|
|
167
209
|
tag: release ? 'CANNOT BE UNDONE' : 'SAFETY CHECK',
|
|
@@ -260,14 +302,17 @@ function renderCard(action) {
|
|
|
260
302
|
lines.push('');
|
|
261
303
|
lines.push(`${chalk.yellow(getGlyphs().attention)} ${chalk.yellow.bold(RULE_LABEL[action.rule])}`);
|
|
262
304
|
lines.push(` ${chalk.dim('op: ')} ${action.op}`);
|
|
263
|
-
|
|
305
|
+
// F8 — a risk card's subject is model-written: cleaned (no terminal escapes)
|
|
306
|
+
// and cut (F9). Other rules print exactly as before.
|
|
307
|
+
const risk = isRiskRule(action);
|
|
308
|
+
lines.push(` ${chalk.dim('object: ')} ${action.object.type} ${risk ? riskSubject(action.object.name) : action.object.name}`);
|
|
264
309
|
lines.push(` ${chalk.dim('transport:')} ${transport}`);
|
|
265
310
|
if (action.rule === 'RULE_8_BATCH_PLAN' && typeof action.batch_count === 'number') {
|
|
266
311
|
lines.push(` ${chalk.dim('batch: ')} ${action.batch_count} objects this turn`);
|
|
267
312
|
}
|
|
268
313
|
// what_will_happen may include embedded newlines (e.g. Rule 8 plan summary).
|
|
269
314
|
// Indent the continuation lines so they align under the header field.
|
|
270
|
-
const whatLines = action.what_will_happen.split('\n');
|
|
315
|
+
const whatLines = risk ? action.what_will_happen.split('\n').map(clean) : action.what_will_happen.split('\n');
|
|
271
316
|
if (whatLines.length === 1) {
|
|
272
317
|
lines.push(` ${chalk.dim('what: ')} ${whatLines[0]}`);
|
|
273
318
|
}
|
|
@@ -6,9 +6,12 @@
|
|
|
6
6
|
* credits existed: ` spend: $1.37`.
|
|
7
7
|
*/
|
|
8
8
|
import { formatCost } from '../cost/pricing.js';
|
|
9
|
-
import { creditsActive, formatCredits, CREDITS_PENDING } from '../cost/credits.js';
|
|
9
|
+
import { creditsActive, tokensOnlyActive, formatCredits, CREDITS_PENDING } from '../cost/credits.js';
|
|
10
|
+
import { AZURE_INVOICE_LINE } from '../cost/display-mode.js';
|
|
10
11
|
/** The spend VALUE in the current display mode (piece 2: goodbye block). */
|
|
11
12
|
export function formatSessionSpendValue(sessionCostUsd, credits) {
|
|
13
|
+
if (tokensOnlyActive())
|
|
14
|
+
return AZURE_INVOICE_LINE;
|
|
12
15
|
if (!creditsActive())
|
|
13
16
|
return formatCost(sessionCostUsd);
|
|
14
17
|
if (!credits)
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { formatFailure, formatInfo } from '../renderer/notice-shape.js';
|
|
2
|
+
export async function runStartupAccount(deps, io) {
|
|
3
|
+
let licenceCheckedAtMs;
|
|
4
|
+
try {
|
|
5
|
+
const cfgEarly = await deps.loadConfig();
|
|
6
|
+
if (cfgEarly.llm.mode === 'managed') {
|
|
7
|
+
const authFile = await deps.readAuthFile();
|
|
8
|
+
const startupKey = authFile ? (await deps.resolveApiKey()).key : null;
|
|
9
|
+
if (authFile && startupKey) {
|
|
10
|
+
const meResult = await deps.fetchMe({ proxyUrl: cfgEarly.proxy_url, apiKey: startupKey });
|
|
11
|
+
if (!meResult.ok) {
|
|
12
|
+
if (meResult.status === 401) {
|
|
13
|
+
io.closeBrand();
|
|
14
|
+
io.print(formatFailure({ what: 'Your CSPeach sign-in is no longer valid', next: 'cspeach login' }));
|
|
15
|
+
return { exit: 1 };
|
|
16
|
+
}
|
|
17
|
+
io.notice(formatInfo(`Could not refresh your account (${meResult.error})`));
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
io.showProfile(meResult.profile);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
const { key: startupKey } = await deps.resolveApiKey();
|
|
26
|
+
if (startupKey) {
|
|
27
|
+
const licence = await deps.runLicenceCheck({ proxyUrl: cfgEarly.proxy_url, apiKey: startupKey }, { headers: await deps.licenceCheckHeaders(cfgEarly.llm.mode).catch(() => ({})) });
|
|
28
|
+
licenceCheckedAtMs = (deps.now ?? Date.now)();
|
|
29
|
+
if (!licence.allow) {
|
|
30
|
+
io.closeBrand();
|
|
31
|
+
io.print(licence.message);
|
|
32
|
+
return { exit: 1 };
|
|
33
|
+
}
|
|
34
|
+
if (licence.verdict === 'unknown') {
|
|
35
|
+
io.notice(formatInfo(licence.line));
|
|
36
|
+
}
|
|
37
|
+
else if (licence.profile) {
|
|
38
|
+
io.showProfile(licence.profile);
|
|
39
|
+
}
|
|
40
|
+
if (licence.verdict === 'ok') {
|
|
41
|
+
try {
|
|
42
|
+
deps.startSkillsWarmup?.(cfgEarly, licence.verdict);
|
|
43
|
+
}
|
|
44
|
+
catch { /* never blocks start-up */ }
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
catch { /* banner is best-effort — never block REPL startup */ }
|
|
50
|
+
return licenceCheckedAtMs === undefined ? { exit: null } : { exit: null, licenceCheckedAtMs };
|
|
51
|
+
}
|