@cspeach/cli 0.6.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/LICENSE +8 -0
- package/README.md +108 -0
- package/dist/agent/anthropic-provider.js +59 -0
- package/dist/agent/llm-provider.js +1 -0
- package/dist/agent/loop.js +709 -0
- package/dist/agent/maybe-build-project-context.js +126 -0
- package/dist/agent/providers/ai-hub-provider.js +58 -0
- package/dist/agent/providers/byok-provider.js +53 -0
- package/dist/agent/providers/factory.js +13 -0
- package/dist/agent/providers/local-provider.js +125 -0
- package/dist/agent/repair-partial.js +31 -0
- package/dist/agent/retry-key.js +58 -0
- package/dist/agent/retry.js +17 -0
- package/dist/agent/sap-connection-adapter.js +82 -0
- package/dist/agent/skill-checkpoint.js +119 -0
- package/dist/agent/tool-dispatch.js +47 -0
- package/dist/agent/turn-assistant-text.js +49 -0
- package/dist/agent/turn-error-ux.js +126 -0
- package/dist/agent/turn-stream.js +79 -0
- package/dist/agent/turn-watchdog.js +71 -0
- package/dist/approvals/advisory-prompt.js +40 -0
- package/dist/approvals/advisory-render.js +38 -0
- package/dist/approvals/approval-prompt.js +100 -0
- package/dist/approvals/jwt.js +33 -0
- package/dist/approvals/render.js +211 -0
- package/dist/approvals/risk-floor.js +26 -0
- package/dist/auth/api-key.js +40 -0
- package/dist/auth/auth-file.js +59 -0
- package/dist/auth/device.js +8 -0
- package/dist/auth/me.js +19 -0
- package/dist/classifier/client.js +58 -0
- package/dist/cli-args.js +38 -0
- package/dist/cli.js +148 -0
- package/dist/commands/config-set.js +245 -0
- package/dist/commands/config-show.js +159 -0
- package/dist/commands/help.js +93 -0
- package/dist/commands/login.js +122 -0
- package/dist/commands/logout.js +17 -0
- package/dist/commands/project-context-impact.js +215 -0
- package/dist/commands/reroute.js +60 -0
- package/dist/commands/spec-gap-status.js +52 -0
- package/dist/commands/whoami.js +35 -0
- package/dist/config/loader.js +67 -0
- package/dist/config/paths.js +20 -0
- package/dist/doctor/checks/_http-probe.js +56 -0
- package/dist/doctor/checks/auth.js +15 -0
- package/dist/doctor/checks/cert.js +24 -0
- package/dist/doctor/checks/forge-rules.js +102 -0
- package/dist/doctor/checks/keychain-fallback.js +14 -0
- package/dist/doctor/checks/keychain.js +23 -0
- package/dist/doctor/checks/llm-mode.js +27 -0
- package/dist/doctor/checks/proxy.js +13 -0
- package/dist/doctor/checks/sap.js +33 -0
- package/dist/doctor/checks/skill.js +24 -0
- package/dist/doctor/checks/write-mode.js +34 -0
- package/dist/doctor/checks/zcspeach.js +76 -0
- package/dist/doctor/run.js +46 -0
- package/dist/errors/codes.js +12 -0
- package/dist/index.js +6 -0
- package/dist/lock-contention.js +22 -0
- package/dist/one-shot.js +104 -0
- package/dist/project-context/conventions.js +309 -0
- package/dist/project-context/detect.js +250 -0
- package/dist/project-context/domain/abap-cloud.js +26 -0
- package/dist/project-context/domain/abapgit.js +177 -0
- package/dist/project-context/domain/cap.js +164 -0
- package/dist/project-context/domain/fiori.js +326 -0
- package/dist/project-context/git.js +115 -0
- package/dist/project-context/index-files.js +235 -0
- package/dist/project-context/index.js +117 -0
- package/dist/project-context/render.js +308 -0
- package/dist/project-context/types.js +14 -0
- package/dist/projects/build.js +20 -0
- package/dist/projects/canonicalize.js +39 -0
- package/dist/projects/email-template.js +54 -0
- package/dist/projects/extract-cca.js +139 -0
- package/dist/projects/extract-design.js +107 -0
- package/dist/projects/extract-estimate.js +93 -0
- package/dist/projects/extract-modernize.js +130 -0
- package/dist/projects/extract-spec-gap.js +101 -0
- package/dist/projects/extract-test-coverage.js +137 -0
- package/dist/projects/extract-upgrade.js +230 -0
- package/dist/projects/filename.js +18 -0
- package/dist/projects/index.js +8 -0
- package/dist/projects/migration.js +111 -0
- package/dist/projects/promote-command.js +96 -0
- package/dist/projects/promote.js +107 -0
- package/dist/projects/save-command.js +124 -0
- package/dist/projects/save.js +21 -0
- package/dist/projects/status.js +170 -0
- package/dist/projects/types.js +1 -0
- package/dist/projects/validate.js +146 -0
- package/dist/projects/workspace.js +478 -0
- package/dist/renderer/abap-inline.js +121 -0
- package/dist/renderer/banners.js +39 -0
- package/dist/renderer/highlighters/abap.js +126 -0
- package/dist/renderer/highlighters/bdef.js +81 -0
- package/dist/renderer/highlighters/cds.js +91 -0
- package/dist/renderer/markdown.js +291 -0
- package/dist/renderer/pipeline.js +201 -0
- package/dist/renderer/progress-chatter.js +237 -0
- package/dist/renderer/question-normalizer.js +306 -0
- package/dist/renderer/severity.js +61 -0
- package/dist/renderer/status-footer.js +50 -0
- package/dist/renderer/syntax.js +58 -0
- package/dist/renderer/tables.js +55 -0
- package/dist/renderer/thinking-heartbeat.js +70 -0
- package/dist/renderer/tool-widget.js +199 -0
- package/dist/renderer/tty.js +66 -0
- package/dist/renderer/widget-extractor.js +87 -0
- package/dist/renderer/widget-fallback.js +78 -0
- package/dist/renderer/widget-schemas.js +43 -0
- package/dist/repl/at-completer.js +64 -0
- package/dist/repl/at-picker.js +122 -0
- package/dist/repl/bracketed-paste.js +284 -0
- package/dist/repl/current-transport.js +46 -0
- package/dist/repl/diff-display.js +41 -0
- package/dist/repl/file-picker.js +219 -0
- package/dist/repl/inquirer-guard.js +130 -0
- package/dist/repl/inquirer-theme.js +41 -0
- package/dist/repl/rule8-detector.js +99 -0
- package/dist/repl/safety-confirm.js +106 -0
- package/dist/repl/safety-mode-state.js +36 -0
- package/dist/repl/slash-completer.js +59 -0
- package/dist/repl/slash-picker.js +124 -0
- package/dist/repl/update-method-preview-hook.js +45 -0
- package/dist/repl.js +1383 -0
- package/dist/router/classifier.js +38 -0
- package/dist/router/intent-extractor.js +140 -0
- package/dist/router/routing-decision.js +19 -0
- package/dist/sap/connection-manager.js +52 -0
- package/dist/sap/onboarding.js +178 -0
- package/dist/sap/system-info.js +515 -0
- package/dist/session/awaiting-answer.js +73 -0
- package/dist/session/gc.js +28 -0
- package/dist/session/pending.js +37 -0
- package/dist/session/resume.js +77 -0
- package/dist/session/schema.js +20 -0
- package/dist/session/store.js +147 -0
- package/dist/session/time-ago.js +41 -0
- package/dist/skill-catalog.js +222 -0
- package/dist/skills/bundled-skills.js +1 -0
- package/dist/skills/canonical.js +12 -0
- package/dist/skills/manifest-client.js +93 -0
- package/dist/skills/promotion-dispatch.js +24 -0
- package/dist/skills/signing-public-key.js +4 -0
- package/dist/skills/source-bundled.js +20 -0
- package/dist/skills/source-managed.js +26 -0
- package/dist/skills/source-manifest.js +26 -0
- package/dist/tools/_command-shared.js +110 -0
- package/dist/tools/_filesystem-shared.js +81 -0
- package/dist/tools/_flag.js +39 -0
- package/dist/tools/approval.js +228 -0
- package/dist/tools/ask-question.js +205 -0
- package/dist/tools/dispatch-skill.js +81 -0
- package/dist/tools/filesystem/file-edit.js +140 -0
- package/dist/tools/filesystem/file-read.js +89 -0
- package/dist/tools/filesystem/file-write.js +128 -0
- package/dist/tools/filesystem/glob.js +177 -0
- package/dist/tools/filesystem/grep.js +163 -0
- package/dist/tools/index.js +32 -0
- package/dist/tools/project/convention_get.js +91 -0
- package/dist/tools/project/playbook_get.js +132 -0
- package/dist/tools/project/project_context_get.js +101 -0
- package/dist/tools/sap-read.js +454 -0
- package/dist/tools/sap-write.js +746 -0
- package/dist/tools/shell/shell_exec.js +209 -0
- package/dist/tools/snapshot.js +107 -0
- package/dist/tools/subagent/_background-shared.js +133 -0
- package/dist/tools/subagent/agent_run.js +186 -0
- package/dist/tools/subagent/background_run.js +143 -0
- package/dist/tools/subagent/monitor_emit.js +65 -0
- package/dist/tools/subagent/schedule_create.js +131 -0
- package/dist/tools/transport.js +233 -0
- package/dist/tools/update-method-intercept.js +119 -0
- package/dist/tools/verify.js +39 -0
- package/dist/tools/web/_web-shared.js +251 -0
- package/dist/tools/web/web_fetch.js +257 -0
- package/dist/tools/web/web_search.js +195 -0
- package/dist/tools/write-mode.js +22 -0
- package/dist/ui/app.js +95 -0
- package/dist/ui/approval-emitter.js +10 -0
- package/dist/ui/approval-modal.js +53 -0
- package/dist/ui/ascii-chars.js +6 -0
- package/dist/ui/body.js +102 -0
- package/dist/ui/coaching-picker-classic.js +36 -0
- package/dist/ui/coaching-picker-emitter.js +27 -0
- package/dist/ui/command-palette.js +34 -0
- package/dist/ui/error-emitter.js +21 -0
- package/dist/ui/footer.js +103 -0
- package/dist/ui/header.js +17 -0
- package/dist/ui/ink-classifier-route.js +19 -0
- package/dist/ui/login-banner.js +72 -0
- package/dist/ui/rich-error-box.js +9 -0
- package/dist/ui/sap-state-store.js +65 -0
- package/dist/ui/session-timeline.js +31 -0
- package/dist/ui/sidebar.js +10 -0
- package/dist/ui/skill-picker.js +50 -0
- package/dist/ui/status-row.js +12 -0
- package/dist/ui/widget-control.js +4 -0
- package/dist/ui/widgets/bar-chart.js +15 -0
- package/dist/ui/widgets/coaching-picker.js +41 -0
- package/dist/ui/widgets/component-registry.js +12 -0
- package/dist/ui/widgets/dep-graph.js +9 -0
- package/dist/ui/widgets/diff-viewer.js +11 -0
- package/dist/ui/widgets/question-card.js +11 -0
- package/dist/ui/widgets/stack-frames.js +5 -0
- package/dist/upgrade-check.js +28 -0
- package/dist/upgrade.js +13 -0
- package/package.json +83 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import keytar from 'keytar';
|
|
2
|
+
export async function checkKeychain() {
|
|
3
|
+
try {
|
|
4
|
+
const probe = `probe-${Date.now()}`;
|
|
5
|
+
await keytar.setPassword('cspeach', 'probe', probe);
|
|
6
|
+
const ok = (await keytar.getPassword('cspeach', 'probe')) === probe;
|
|
7
|
+
await keytar.deletePassword('cspeach', 'probe');
|
|
8
|
+
if (ok)
|
|
9
|
+
return { name: 'keychain', ok: true, message: 'OS keychain working' };
|
|
10
|
+
return { name: 'keychain', ok: false, message: 'probe write/read mismatch', hint: 'Per-session fallback mode will be used' };
|
|
11
|
+
}
|
|
12
|
+
catch (err) {
|
|
13
|
+
const msg = err.message;
|
|
14
|
+
let hint = 'Per-session fallback mode will be used; API key + SAP passwords will be prompted each session';
|
|
15
|
+
if (process.platform === 'win32' && /credential manager/i.test(msg)) {
|
|
16
|
+
hint = 'Windows Credential Manager may be disabled by GPO ("Network access: Do not allow storage of passwords and credentials"). Ask IT to enable, or use per-session mode.';
|
|
17
|
+
}
|
|
18
|
+
if (process.platform === 'linux' && /libsecret|keyring/i.test(msg)) {
|
|
19
|
+
hint = 'Install libsecret: sudo apt-get install libsecret-1-0 libsecret-1-dev, or use per-session mode';
|
|
20
|
+
}
|
|
21
|
+
return { name: 'keychain', ok: false, message: msg, hint };
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { createProviderForMode } from '../../agent/providers/factory.js';
|
|
2
|
+
import { getStore } from '../../auth/api-key.js';
|
|
3
|
+
export async function checkLlmMode(cfg) {
|
|
4
|
+
const getBearer = async () => {
|
|
5
|
+
const store = await getStore();
|
|
6
|
+
const key = await store.get();
|
|
7
|
+
return key ?? '';
|
|
8
|
+
};
|
|
9
|
+
try {
|
|
10
|
+
const provider = createProviderForMode(cfg, getBearer);
|
|
11
|
+
const h = await provider.healthCheck();
|
|
12
|
+
return {
|
|
13
|
+
name: `llm-mode (${cfg.llm.mode})`,
|
|
14
|
+
ok: h.ok,
|
|
15
|
+
message: `${provider.name}: ${h.message}`,
|
|
16
|
+
hint: h.hint,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
catch (e) {
|
|
20
|
+
return {
|
|
21
|
+
name: `llm-mode (${cfg.llm.mode})`,
|
|
22
|
+
ok: false,
|
|
23
|
+
message: `Failed to construct provider: ${e.message}`,
|
|
24
|
+
hint: 'Check llm.mode config and per-mode fields',
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { loadConfig } from '../../config/loader.js';
|
|
2
|
+
export async function checkProxy() {
|
|
3
|
+
const cfg = await loadConfig();
|
|
4
|
+
try {
|
|
5
|
+
const r = await fetch(`${cfg.proxy_url}/health`, { signal: AbortSignal.timeout(5000) });
|
|
6
|
+
if (!r.ok)
|
|
7
|
+
return { name: 'proxy', ok: false, message: `HTTP ${r.status}`, hint: `Check ${cfg.proxy_url} reachability` };
|
|
8
|
+
return { name: 'proxy', ok: true, message: `${cfg.proxy_url} reachable` };
|
|
9
|
+
}
|
|
10
|
+
catch (err) {
|
|
11
|
+
return { name: 'proxy', ok: false, message: `unreachable (${err.message})`, hint: 'Check firewall, DNS, or proxy URL in ~/.cspeach/config.toml' };
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { loadConfig } from '../../config/loader.js';
|
|
2
|
+
import { probeGet } from './_http-probe.js';
|
|
3
|
+
export async function checkSap() {
|
|
4
|
+
const cfg = await loadConfig();
|
|
5
|
+
const systems = Object.entries(cfg.sap);
|
|
6
|
+
if (systems.length === 0)
|
|
7
|
+
return { name: 'sap', ok: false, message: 'no SAP systems configured', hint: 'Run: cspeach config add-sap' };
|
|
8
|
+
const results = [];
|
|
9
|
+
let anyFailed = false;
|
|
10
|
+
for (const [alias, sys] of systems) {
|
|
11
|
+
const url = `${sys.useSsl ? 'https' : 'http'}://${sys.host}:${sys.port}/sap/bc/adt/discovery`;
|
|
12
|
+
// Use node's https directly so we can honor sslVerify=false for
|
|
13
|
+
// self-signed dev systems — Node's built-in fetch() doesn't expose a
|
|
14
|
+
// per-request TLS-verify toggle. See checks/_http-probe.ts.
|
|
15
|
+
const r = await probeGet(url, {
|
|
16
|
+
rejectUnauthorized: sys.sslVerify,
|
|
17
|
+
timeoutMs: 8000,
|
|
18
|
+
});
|
|
19
|
+
if (r.error) {
|
|
20
|
+
results.push(`${alias}: ${r.error}`);
|
|
21
|
+
anyFailed = true;
|
|
22
|
+
}
|
|
23
|
+
else if (r.status === 200 || r.status === 401) {
|
|
24
|
+
// 200 = anonymous discovery allowed; 401 = reachable + auth-required (expected, fine)
|
|
25
|
+
results.push(`${alias}: reachable`);
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
results.push(`${alias}: HTTP ${r.status}`);
|
|
29
|
+
anyFailed = true;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return { name: 'sap', ok: !anyFailed, message: results.join('; ') };
|
|
33
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import crypto from 'node:crypto';
|
|
2
|
+
import { loadConfig } from '../../config/loader.js';
|
|
3
|
+
import { getStore } from '../../auth/api-key.js';
|
|
4
|
+
export async function checkSkill() {
|
|
5
|
+
const cfg = await loadConfig();
|
|
6
|
+
const store = await getStore();
|
|
7
|
+
const key = await store.get();
|
|
8
|
+
if (!key)
|
|
9
|
+
return { name: 'skill', ok: false, message: 'skipped (no API key)' };
|
|
10
|
+
try {
|
|
11
|
+
const r = await fetch(`${cfg.proxy_url}/v1/skills/abap-radar`, { headers: { Authorization: `Bearer ${key}` } });
|
|
12
|
+
if (!r.ok)
|
|
13
|
+
return { name: 'skill', ok: false, message: `HTTP ${r.status}` };
|
|
14
|
+
const body = await r.text();
|
|
15
|
+
const sha256 = r.headers.get('x-skill-sha256') ?? '';
|
|
16
|
+
const computed = crypto.createHash('sha256').update(body, 'utf-8').digest('hex');
|
|
17
|
+
if (sha256 !== computed)
|
|
18
|
+
return { name: 'skill', ok: false, message: `SHA256 mismatch (header=${sha256}, body=${computed})`, hint: 'Possible tampering or proxy bug — contact support' };
|
|
19
|
+
return { name: 'skill', ok: true, message: `abap-radar fetched (${body.length} bytes, SHA ${sha256.slice(0, 8)}...)` };
|
|
20
|
+
}
|
|
21
|
+
catch (err) {
|
|
22
|
+
return { name: 'skill', ok: false, message: err.message };
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Doctor check: surfaces the current `write_mode` setting and explains its effect.
|
|
3
|
+
*
|
|
4
|
+
* All three modes are valid configurations, so `ok` is always true — this check is
|
|
5
|
+
* informational. Users run `cspeach doctor` to see how CSPeach will behave before
|
|
6
|
+
* they make a change; they shouldn't have to read docs or dig into config to find out.
|
|
7
|
+
*
|
|
8
|
+
* `auto` additionally emits a `hint` nudging the user to review per-SAP auto_approve
|
|
9
|
+
* levels, since that's where the actual gating lives when write_mode is auto.
|
|
10
|
+
*/
|
|
11
|
+
export async function checkWriteMode(cfg) {
|
|
12
|
+
const mode = cfg.write_mode;
|
|
13
|
+
switch (mode) {
|
|
14
|
+
case 'advisory-only':
|
|
15
|
+
return {
|
|
16
|
+
name: 'write-mode',
|
|
17
|
+
ok: true,
|
|
18
|
+
message: 'write_mode = advisory-only — no SAP writes will occur; CSPeach proposes, developer applies',
|
|
19
|
+
};
|
|
20
|
+
case 'approval-gated':
|
|
21
|
+
return {
|
|
22
|
+
name: 'write-mode',
|
|
23
|
+
ok: true,
|
|
24
|
+
message: 'write_mode = approval-gated — CSPeach asks before each write (recommended default)',
|
|
25
|
+
};
|
|
26
|
+
case 'auto':
|
|
27
|
+
return {
|
|
28
|
+
name: 'write-mode',
|
|
29
|
+
ok: true,
|
|
30
|
+
message: 'write_mode = auto — some writes may proceed without prompt based on per-SAP auto_approve level',
|
|
31
|
+
hint: 'Review auto_approve settings per SAP system via `cspeach config show`',
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import keytar from 'keytar';
|
|
2
|
+
import { loadConfig } from '../../config/loader.js';
|
|
3
|
+
import { probeGet } from './_http-probe.js';
|
|
4
|
+
const SAP_SERVICE = 'cspeach-sap';
|
|
5
|
+
export async function checkZcspeach() {
|
|
6
|
+
const cfg = await loadConfig();
|
|
7
|
+
const systems = Object.entries(cfg.sap);
|
|
8
|
+
if (systems.length === 0) {
|
|
9
|
+
return {
|
|
10
|
+
name: 'zcspeach',
|
|
11
|
+
ok: false,
|
|
12
|
+
message: 'no SAP systems configured',
|
|
13
|
+
hint: 'Run: cspeach config add-sap',
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
const results = [];
|
|
17
|
+
let anyFailed = false;
|
|
18
|
+
for (const [alias, sys] of systems) {
|
|
19
|
+
const protocol = sys.useSsl ? 'https' : 'http';
|
|
20
|
+
const url = `${protocol}://${sys.host}:${sys.port}/sap/bc/zcspeach/discovery`;
|
|
21
|
+
let pwd = null;
|
|
22
|
+
try {
|
|
23
|
+
pwd = await keytar.getPassword(SAP_SERVICE, alias);
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
// keychain unavailable — fall through, will be skipped below
|
|
27
|
+
}
|
|
28
|
+
if (!pwd) {
|
|
29
|
+
results.push(`${alias}: skipped (no password in keychain)`);
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
// Use node's https directly so we can honor sslVerify=false for
|
|
33
|
+
// self-signed dev systems. See checks/_http-probe.ts.
|
|
34
|
+
const r = await probeGet(url, {
|
|
35
|
+
rejectUnauthorized: sys.sslVerify,
|
|
36
|
+
auth: `${sys.username}:${pwd}`,
|
|
37
|
+
accept: 'application/json',
|
|
38
|
+
timeoutMs: 5000,
|
|
39
|
+
});
|
|
40
|
+
if (r.error) {
|
|
41
|
+
results.push(`${alias}: ${r.error}`);
|
|
42
|
+
anyFailed = true;
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
if (r.status < 200 || r.status >= 300) {
|
|
46
|
+
results.push(`${alias}: HTTP ${r.status} from /zcspeach/discovery`);
|
|
47
|
+
anyFailed = true;
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
let body;
|
|
51
|
+
try {
|
|
52
|
+
body = JSON.parse(r.body);
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
results.push(`${alias}: invalid JSON from /zcspeach/discovery`);
|
|
56
|
+
anyFailed = true;
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
if (body.status !== 'ok') {
|
|
60
|
+
results.push(`${alias}: discovery returned status=${body.status ?? '<missing>'}`);
|
|
61
|
+
anyFailed = true;
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
const ver = body.data?.version ?? '<missing>';
|
|
65
|
+
const ops = body.data?.operations?.length ?? 0;
|
|
66
|
+
results.push(`${alias}: v${ver}, ${ops} ops`);
|
|
67
|
+
}
|
|
68
|
+
return {
|
|
69
|
+
name: 'zcspeach',
|
|
70
|
+
ok: !anyFailed,
|
|
71
|
+
message: results.join('; '),
|
|
72
|
+
hint: anyFailed
|
|
73
|
+
? 'SICF node /sap/bc/zcspeach must be active; ensure SAP_BASIS supports the framework'
|
|
74
|
+
: undefined,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { loadConfig } from '../config/loader.js';
|
|
3
|
+
import { checkProxy } from './checks/proxy.js';
|
|
4
|
+
import { checkAuth } from './checks/auth.js';
|
|
5
|
+
import { checkKeychain } from './checks/keychain.js';
|
|
6
|
+
import { checkSap } from './checks/sap.js';
|
|
7
|
+
import { checkZcspeach } from './checks/zcspeach.js';
|
|
8
|
+
import { checkCert } from './checks/cert.js';
|
|
9
|
+
import { checkSkill } from './checks/skill.js';
|
|
10
|
+
import { checkKeychainFallback } from './checks/keychain-fallback.js';
|
|
11
|
+
import { checkWriteMode } from './checks/write-mode.js';
|
|
12
|
+
import { checkLlmMode } from './checks/llm-mode.js';
|
|
13
|
+
import { checkForgeRules } from './checks/forge-rules.js';
|
|
14
|
+
export async function runDoctor() {
|
|
15
|
+
console.log(chalk.cyan.bold('\nCSPeach — doctor\n'));
|
|
16
|
+
const cfg = await loadConfig();
|
|
17
|
+
const checks = [];
|
|
18
|
+
checks.push(await checkProxy());
|
|
19
|
+
checks.push(await checkAuth());
|
|
20
|
+
checks.push(await checkLlmMode(cfg));
|
|
21
|
+
checks.push(await checkWriteMode(cfg));
|
|
22
|
+
checks.push(await checkKeychain());
|
|
23
|
+
checks.push(await checkSap());
|
|
24
|
+
checks.push(await checkZcspeach());
|
|
25
|
+
checks.push(await checkCert());
|
|
26
|
+
checks.push(await checkSkill());
|
|
27
|
+
checks.push(await checkKeychainFallback());
|
|
28
|
+
checks.push(await checkForgeRules());
|
|
29
|
+
let failed = 0;
|
|
30
|
+
for (const c of checks) {
|
|
31
|
+
const icon = c.skipped
|
|
32
|
+
? chalk.yellow('- ')
|
|
33
|
+
: c.ok
|
|
34
|
+
? chalk.green('OK ')
|
|
35
|
+
: chalk.red('X ');
|
|
36
|
+
console.log(` ${icon} ${c.name.padEnd(22)} ${c.message}`);
|
|
37
|
+
if (c.hint)
|
|
38
|
+
console.log(chalk.gray(` -> ${c.hint}`));
|
|
39
|
+
if (!c.ok && !c.skipped)
|
|
40
|
+
failed++;
|
|
41
|
+
}
|
|
42
|
+
console.log(failed === 0
|
|
43
|
+
? chalk.green('\nAll checks passed.\n')
|
|
44
|
+
: chalk.yellow(`\n${failed} check(s) failed.\n`));
|
|
45
|
+
return failed === 0 ? 0 : 1;
|
|
46
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error taxonomy — canonical error code constants used across CSPeach CLI tools.
|
|
3
|
+
*/
|
|
4
|
+
export const ERR = {
|
|
5
|
+
APPROVAL_THRASH: 'APPROVAL_THRASH',
|
|
6
|
+
APPROVAL_INVALID: 'APPROVAL_INVALID',
|
|
7
|
+
SNAPSHOT_FAILED: 'SNAPSHOT_FAILED',
|
|
8
|
+
SYNTAX_ERRORS: 'SYNTAX_ERRORS',
|
|
9
|
+
SAP_UNREACHABLE: 'SAP_UNREACHABLE',
|
|
10
|
+
LOCK_HELD_BY_OTHER: 'LOCK_HELD_BY_OTHER',
|
|
11
|
+
SCHEMA_VERSION_MISMATCH: 'SCHEMA_VERSION_MISMATCH',
|
|
12
|
+
};
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { select } from '@inquirer/prompts';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
export async function handleLockConflict(info) {
|
|
4
|
+
console.log(chalk.yellow(`\n⚠ ${info.object} is locked by ${info.user} since ${info.lockTime}.`));
|
|
5
|
+
return (await select({
|
|
6
|
+
message: 'How do you want to proceed?',
|
|
7
|
+
choices: [
|
|
8
|
+
{ value: 'wait', name: 'Wait (retry every 10s for up to 2 minutes)' },
|
|
9
|
+
{ value: 'skip', name: 'Skip this object (tell the model to work on something else)' },
|
|
10
|
+
{ value: 'cancel', name: 'Cancel turn' },
|
|
11
|
+
],
|
|
12
|
+
}));
|
|
13
|
+
}
|
|
14
|
+
export async function pollLock(checkFn, maxSeconds = 120) {
|
|
15
|
+
const startedAt = Date.now();
|
|
16
|
+
while (Date.now() - startedAt < maxSeconds * 1000) {
|
|
17
|
+
await new Promise((r) => setTimeout(r, 10_000));
|
|
18
|
+
if (await checkFn())
|
|
19
|
+
return true;
|
|
20
|
+
}
|
|
21
|
+
return false;
|
|
22
|
+
}
|
package/dist/one-shot.js
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { loadConfig } from './config/loader.js';
|
|
3
|
+
import { getStore } from './auth/api-key.js';
|
|
4
|
+
import { getConnection } from './sap/connection-manager.js';
|
|
5
|
+
import { AdtClient } from '@cspeach/sap-client';
|
|
6
|
+
import { makeSessionId, saveSession } from './session/store.js';
|
|
7
|
+
import { newSession } from './session/schema.js';
|
|
8
|
+
import { createProviderForMode } from './agent/providers/factory.js';
|
|
9
|
+
import { runTurn } from './agent/loop.js';
|
|
10
|
+
import { classifyPrompt, parseShortcut } from './router/classifier.js';
|
|
11
|
+
import { setRenderingOverride } from './renderer/tty.js';
|
|
12
|
+
import { alwaysDeclinePreviewHook } from './repl/update-method-preview-hook.js';
|
|
13
|
+
// Side-effect imports for tool registration.
|
|
14
|
+
import './tools/sap-read.js';
|
|
15
|
+
import './tools/sap-write.js';
|
|
16
|
+
import './tools/transport.js';
|
|
17
|
+
import './tools/approval.js';
|
|
18
|
+
import './tools/snapshot.js';
|
|
19
|
+
import './tools/ask-question.js';
|
|
20
|
+
import './tools/dispatch-skill.js';
|
|
21
|
+
import './tools/filesystem/file-read.js';
|
|
22
|
+
import './tools/filesystem/file-edit.js';
|
|
23
|
+
import './tools/filesystem/file-write.js';
|
|
24
|
+
import './tools/filesystem/glob.js';
|
|
25
|
+
import './tools/filesystem/grep.js';
|
|
26
|
+
import './tools/shell/shell_exec.js';
|
|
27
|
+
import './tools/web/web_fetch.js';
|
|
28
|
+
import './tools/web/web_search.js';
|
|
29
|
+
import './tools/project/project_context_get.js';
|
|
30
|
+
import './tools/project/playbook_get.js';
|
|
31
|
+
import './tools/project/convention_get.js';
|
|
32
|
+
import './tools/subagent/_background-shared.js';
|
|
33
|
+
import './tools/subagent/background_run.js';
|
|
34
|
+
import './tools/subagent/monitor_emit.js';
|
|
35
|
+
import './tools/subagent/schedule_create.js';
|
|
36
|
+
import './tools/subagent/agent_run.js';
|
|
37
|
+
export async function runOneShot(prompt, opts) {
|
|
38
|
+
const cfg = await loadConfig();
|
|
39
|
+
setRenderingOverride(cfg.ui.rendering);
|
|
40
|
+
// Task C.10 — advisory-only is the only mode the one-shot announces, because
|
|
41
|
+
// non-interactive output should stay minimal. The user needs to know that
|
|
42
|
+
// writes will NOT be executed before a skill that normally writes runs.
|
|
43
|
+
if (cfg.write_mode === 'advisory-only') {
|
|
44
|
+
console.log(chalk.yellow('⚠ Advisory mode: writes will be proposed, not executed.'));
|
|
45
|
+
}
|
|
46
|
+
const store = await getStore();
|
|
47
|
+
if (!(await store.get())) {
|
|
48
|
+
console.error('No API key configured. Run `cspeach` (interactive) first to set one.');
|
|
49
|
+
return 1;
|
|
50
|
+
}
|
|
51
|
+
const aliases = Object.keys(cfg.sap);
|
|
52
|
+
if (aliases.length === 0) {
|
|
53
|
+
console.error('No SAP system configured. Run `cspeach config add-sap` first.');
|
|
54
|
+
return 1;
|
|
55
|
+
}
|
|
56
|
+
// C.1 — --sap-alias flag override
|
|
57
|
+
const alias = opts?.sapAlias ?? aliases[0];
|
|
58
|
+
if (opts?.sapAlias) {
|
|
59
|
+
console.log(chalk.dim(`Using --sap-alias ${opts.sapAlias}`));
|
|
60
|
+
}
|
|
61
|
+
const conn = await getConnection(alias, cfg.sap[alias]);
|
|
62
|
+
const adt = new AdtClient(conn);
|
|
63
|
+
const session = newSession(makeSessionId(), alias, null, cfg.default_model);
|
|
64
|
+
await saveSession(session);
|
|
65
|
+
const parsed = parseShortcut(prompt);
|
|
66
|
+
const body = parsed.prompt;
|
|
67
|
+
let skill;
|
|
68
|
+
if (parsed.skill) {
|
|
69
|
+
skill = parsed.skill;
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
// One-shot has no interactive picker — always uses top-1 and prints the
|
|
73
|
+
// confidence band on a dim prelude line. If < 0.6, emit a stderr warning
|
|
74
|
+
// with top-3 alternates so the user can rerun with an explicit /skill.
|
|
75
|
+
const c = await classifyPrompt(body, {
|
|
76
|
+
sap_system: alias,
|
|
77
|
+
last_skill: session.last_skill ?? null,
|
|
78
|
+
last_object: parsed.object ?? session.last_object ?? null,
|
|
79
|
+
});
|
|
80
|
+
const pct = Math.round(c.confidence * 100);
|
|
81
|
+
if (c.confidence < 0.6) {
|
|
82
|
+
const alternates = [c.skill, ...c.alternates].slice(0, 3).map((s) => `/${s}`).join(', ');
|
|
83
|
+
console.error(chalk.yellow(`[classifier] Low confidence (${pct}%). Using /${c.skill}. ` +
|
|
84
|
+
`Candidates: ${alternates}. Rerun with an explicit /skill to override.`));
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
console.log(chalk.dim(`┄ Routed to /${c.skill} (${pct}%)`));
|
|
88
|
+
}
|
|
89
|
+
skill = c.skill;
|
|
90
|
+
}
|
|
91
|
+
const provider = createProviderForMode(cfg, async () => {
|
|
92
|
+
const key = await store.get();
|
|
93
|
+
if (!key)
|
|
94
|
+
throw new Error('No API key configured');
|
|
95
|
+
return key;
|
|
96
|
+
});
|
|
97
|
+
await runTurn({
|
|
98
|
+
provider,
|
|
99
|
+
userMessage: body,
|
|
100
|
+
skill,
|
|
101
|
+
ctx: { adt, sapAlias: alias, session, cwd: process.cwd(), provider, skillSource: provider.skillSource, previewHook: alwaysDeclinePreviewHook },
|
|
102
|
+
});
|
|
103
|
+
return 0;
|
|
104
|
+
}
|