@todoist/doistbot-cli 0.0.0-stage → 1.0.16
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/actions/auth.js +225 -0
- package/dist/actions/doctor.js +39 -0
- package/dist/actions/review-usage.js +25 -0
- package/dist/actions/review.js +311 -0
- package/dist/actions/setup.js +14 -0
- package/dist/auth.js +202 -0
- package/dist/config.js +145 -0
- package/dist/env.js +10 -0
- package/dist/file-store.js +112 -0
- package/dist/index.js +164 -0
- package/dist/input.js +40 -0
- package/dist/runtime.js +105 -0
- package/dist/secure-store.js +148 -0
- package/dist/terminal.js +185 -0
- package/dist/test-utils/secure-store-mocks.js +29 -0
- package/dist/update-cache-entrypoint.js +16 -0
- package/dist/update-check.js +235 -0
- package/package.json +65 -5
- package/sandbox/_node_modules/doistbot-logging/dist/index.d.ts +23 -0
- package/sandbox/_node_modules/doistbot-logging/dist/index.js +51 -0
- package/sandbox/_node_modules/doistbot-logging/package.json +12 -0
- package/sandbox/_node_modules/doistbot-repo-config/dist/index.d.ts +93 -0
- package/sandbox/_node_modules/doistbot-repo-config/dist/index.js +197 -0
- package/sandbox/_node_modules/doistbot-repo-config/package.json +12 -0
- package/sandbox/dist/core/autofix-resources.js +58 -0
- package/sandbox/dist/core/autofix-service-query.js +69 -0
- package/sandbox/dist/core/autofix-test-policy.js +11 -0
- package/sandbox/dist/core/autofix-verification.js +355 -0
- package/sandbox/dist/core/bot-logins.js +14 -0
- package/sandbox/dist/core/check-run.js +158 -0
- package/sandbox/dist/core/datadog-metrics.js +113 -0
- package/sandbox/dist/core/environment.js +49 -0
- package/sandbox/dist/core/git-auth.js +17 -0
- package/sandbox/dist/core/github-history.js +4 -0
- package/sandbox/dist/core/github-issues.js +18 -0
- package/sandbox/dist/core/label-parsing.js +1 -0
- package/sandbox/dist/core/langfuse-observation.js +33 -0
- package/sandbox/dist/core/langfuse-session.js +39 -0
- package/sandbox/dist/core/logger-span-processor.js +90 -0
- package/sandbox/dist/core/logging.js +18 -0
- package/sandbox/dist/core/pi-autofix-extension.js +188 -0
- package/sandbox/dist/core/pi-execution-limits-extension.js +79 -0
- package/sandbox/dist/core/pi-progress-logging.js +51 -0
- package/sandbox/dist/core/pi-tool-output-filter-extension.js +65 -0
- package/sandbox/dist/core/pi.js +870 -0
- package/sandbox/dist/core/pii-scrubber.js +31 -0
- package/sandbox/dist/core/prompt-template.js +63 -0
- package/sandbox/dist/core/reactions.js +36 -0
- package/sandbox/dist/core/regexp.js +3 -0
- package/sandbox/dist/core/repository-map.js +1 -0
- package/sandbox/dist/core/repository-resolver.js +1 -0
- package/sandbox/dist/core/retry.js +29 -0
- package/sandbox/dist/core/s3-utils.js +42 -0
- package/sandbox/dist/core/shared.js +124 -0
- package/sandbox/dist/core/snapshot-helpers.js +17 -0
- package/sandbox/dist/core/span-test-helpers.js +13 -0
- package/sandbox/dist/core/tool-environment.js +83 -0
- package/sandbox/dist/core/tool-output.js +18 -0
- package/sandbox/dist/core/tracing-setup.js +92 -0
- package/sandbox/dist/core/tracing.js +81 -0
- package/sandbox/dist/main.js +51 -0
- package/sandbox/dist/providers/config.js +23 -0
- package/sandbox/dist/providers/credentials.js +40 -0
- package/sandbox/dist/providers/gemini.js +28 -0
- package/sandbox/dist/providers/helpers.js +253 -0
- package/sandbox/dist/providers/index.js +27 -0
- package/sandbox/dist/providers/openai.js +23 -0
- package/sandbox/dist/providers/openrouter-pi.js +25 -0
- package/sandbox/dist/providers/pi-review.js +301 -0
- package/sandbox/dist/providers/process.js +34 -0
- package/sandbox/dist/providers/schemas.js +61 -0
- package/sandbox/dist/providers/types.js +4 -0
- package/sandbox/dist/tasks/chat/chat.js +440 -0
- package/sandbox/dist/tasks/chat/prompt.js +20 -0
- package/sandbox/dist/tasks/issue-fix-retry/fix-retry.js +304 -0
- package/sandbox/dist/tasks/issue-summarize/context.js +21 -0
- package/sandbox/dist/tasks/issue-summarize/media.js +335 -0
- package/sandbox/dist/tasks/issue-summarize/model.js +230 -0
- package/sandbox/dist/tasks/issue-summarize/output.js +45 -0
- package/sandbox/dist/tasks/issue-summarize/prompt.js +11 -0
- package/sandbox/dist/tasks/issue-summarize/summarize.js +238 -0
- package/sandbox/dist/tasks/issue-summarize/thread.js +123 -0
- package/sandbox/dist/tasks/issue-summarize/types.js +1 -0
- package/sandbox/dist/tasks/issue-triage/assignee-review.js +114 -0
- package/sandbox/dist/tasks/issue-triage/autofix-capabilities.js +22 -0
- package/sandbox/dist/tasks/issue-triage/autofix-checkpoint.js +122 -0
- package/sandbox/dist/tasks/issue-triage/autofix-policy.js +36 -0
- package/sandbox/dist/tasks/issue-triage/clone.js +40 -0
- package/sandbox/dist/tasks/issue-triage/context.js +57 -0
- package/sandbox/dist/tasks/issue-triage/diff-validator.js +102 -0
- package/sandbox/dist/tasks/issue-triage/fix-attempt.js +617 -0
- package/sandbox/dist/tasks/issue-triage/fix-context.js +5 -0
- package/sandbox/dist/tasks/issue-triage/fix-dispatch.js +263 -0
- package/sandbox/dist/tasks/issue-triage/fix-failure-reasons.js +2 -0
- package/sandbox/dist/tasks/issue-triage/fix-loop/effects.js +526 -0
- package/sandbox/dist/tasks/issue-triage/fix-loop/effects.types.js +1 -0
- package/sandbox/dist/tasks/issue-triage/fix-loop/fake-effects.js +65 -0
- package/sandbox/dist/tasks/issue-triage/fix-loop/promotion-task.js +108 -0
- package/sandbox/dist/tasks/issue-triage/fix-loop/recovery.js +512 -0
- package/sandbox/dist/tasks/issue-triage/fix-loop/runners.js +233 -0
- package/sandbox/dist/tasks/issue-triage/fix-loop/state-machine.js +171 -0
- package/sandbox/dist/tasks/issue-triage/github-request.js +2 -0
- package/sandbox/dist/tasks/issue-triage/hero-group-map.js +206 -0
- package/sandbox/dist/tasks/issue-triage/markers.js +6 -0
- package/sandbox/dist/tasks/issue-triage/model.js +124 -0
- package/sandbox/dist/tasks/issue-triage/output.js +164 -0
- package/sandbox/dist/tasks/issue-triage/pr-creator.js +585 -0
- package/sandbox/dist/tasks/issue-triage/pr-readiness.js +25 -0
- package/sandbox/dist/tasks/issue-triage/prompt.js +63 -0
- package/sandbox/dist/tasks/issue-triage/repo-config.js +47 -0
- package/sandbox/dist/tasks/issue-triage/repo-prompts.js +77 -0
- package/sandbox/dist/tasks/issue-triage/report-fixture.js +53 -0
- package/sandbox/dist/tasks/issue-triage/report-history.js +113 -0
- package/sandbox/dist/tasks/issue-triage/report-schema.js +63 -0
- package/sandbox/dist/tasks/issue-triage/report.js +111 -0
- package/sandbox/dist/tasks/issue-triage/resolution.js +30 -0
- package/sandbox/dist/tasks/issue-triage/routing.js +169 -0
- package/sandbox/dist/tasks/issue-triage/sandbox-environment.js +59 -0
- package/sandbox/dist/tasks/issue-triage/shared.js +86 -0
- package/sandbox/dist/tasks/issue-triage/side-effects.js +102 -0
- package/sandbox/dist/tasks/issue-triage/triage.js +442 -0
- package/sandbox/dist/tasks/issue-triage/types.js +1 -0
- package/sandbox/dist/tasks/persist-logs/context.js +19 -0
- package/sandbox/dist/tasks/persist-logs/dd-proxy.js +116 -0
- package/sandbox/dist/tasks/persist-logs/llm-query-planner.js +156 -0
- package/sandbox/dist/tasks/persist-logs/persist-logs.js +491 -0
- package/sandbox/dist/tasks/refresh-s3-urls/refresh-s3-urls.js +214 -0
- package/sandbox/dist/tasks/review/check-run.js +326 -0
- package/sandbox/dist/tasks/review/comment-mapper.js +46 -0
- package/sandbox/dist/tasks/review/conversation-context.js +477 -0
- package/sandbox/dist/tasks/review/diff.js +95 -0
- package/sandbox/dist/tasks/review/engines/dedupe.js +291 -0
- package/sandbox/dist/tasks/review/engines/multi-focus.js +419 -0
- package/sandbox/dist/tasks/review/engines/shared.js +87 -0
- package/sandbox/dist/tasks/review/engines/types.js +1 -0
- package/sandbox/dist/tasks/review/finding-output.js +18 -0
- package/sandbox/dist/tasks/review/format.js +17 -0
- package/sandbox/dist/tasks/review/github-comment-format.js +94 -0
- package/sandbox/dist/tasks/review/local-git.js +374 -0
- package/sandbox/dist/tasks/review/local.js +118 -0
- package/sandbox/dist/tasks/review/multi-focus-prompt.js +59 -0
- package/sandbox/dist/tasks/review/prompt.js +81 -0
- package/sandbox/dist/tasks/review/review-feedback.js +48 -0
- package/sandbox/dist/tasks/review/review-mode-github.js +58 -0
- package/sandbox/dist/tasks/review/review-mode-policy.js +232 -0
- package/sandbox/dist/tasks/review/review-mode-types.js +1 -0
- package/sandbox/dist/tasks/review/review-mode.js +296 -0
- package/sandbox/dist/tasks/review/review-summary.js +123 -0
- package/sandbox/dist/tasks/review/review.js +391 -0
- package/sandbox/dist/tasks/review/runner.js +255 -0
- package/sandbox/dist/tasks/review/severity.js +5 -0
- package/sandbox/dist/tasks/review/summary-model.js +271 -0
- package/sandbox/dist/tasks/review/thinking-level.js +14 -0
- package/sandbox/dist/tasks/review/translation-review-policy.js +37 -0
- package/sandbox/dist/tasks/review/types.js +1 -0
- package/sandbox/dist/tasks/review-slice-plan/context.js +66 -0
- package/sandbox/dist/tasks/review-slice-plan/plan.js +210 -0
- package/sandbox/dist/tasks/review-slice-plan/prompt.js +59 -0
- package/sandbox/dist/tasks/review-slice-plan/review-slice-plan.js +367 -0
- package/sandbox/dist/tasks/sentry-triage/store.js +133 -0
- package/sandbox/dist/tasks/sentry-triage/triage.js +217 -0
- package/sandbox/node_modules/doistbot-logging/dist/index.d.ts +23 -0
- package/sandbox/node_modules/doistbot-logging/dist/index.js +51 -0
- package/sandbox/node_modules/doistbot-logging/package.json +12 -0
- package/sandbox/node_modules/doistbot-repo-config/dist/index.d.ts +93 -0
- package/sandbox/node_modules/doistbot-repo-config/dist/index.js +197 -0
- package/sandbox/node_modules/doistbot-repo-config/package.json +12 -0
- package/sandbox/src/review/prompts/ai-internal-tools.md +254 -0
- package/sandbox/src/review/prompts/backend-repository-standards.md +102 -0
- package/sandbox/src/review/prompts/observability-standard.md +169 -0
- package/sandbox/src/review/prompts/outline-map.json +26 -0
- package/sandbox/src/review/prompts/secrets-management.md +286 -0
- package/sandbox/src/review/prompts/service-production-readiness.md +122 -0
- package/sandbox/src/review/prompts/workload-placement.md +345 -0
- package/sandbox/src/tasks/review/prompts/review-focus-prompts/efficiency.md +16 -0
- package/sandbox/src/tasks/review/prompts/review-focus-prompts/general.md +32 -0
- package/sandbox/src/tasks/review/prompts/review-focus-prompts/quality.md +20 -0
- package/sandbox/src/tasks/review/prompts/review-focus-prompts/reuse.md +13 -0
- package/sandbox/src/tasks/review/prompts/review-focus-prompts/security.md +18 -0
- package/sandbox/src/tasks/review/prompts/review-focus-prompts/standards.md +21 -0
- package/sandbox/src/tasks/review/prompts/review-focus-prompts/tests.md +19 -0
- package/sandbox/src/tasks/review/prompts/review-mode.md +40 -0
- package/sandbox/src/tasks/review/prompts/review-multi-focus-base-prompt.md +142 -0
- package/scripts/restore-runtime-assets.mjs +21 -0
- package/README.md +0 -3
package/dist/runtime.js
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { execFileSync } from 'child_process';
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { fileURLToPath, pathToFileURL } from 'url';
|
|
5
|
+
const SANDBOX_REVIEW_MODULE_PATH = ['tasks', 'review', 'local.js'];
|
|
6
|
+
const SANDBOX_FORMAT_MODULE_PATH = ['tasks', 'review', 'format.js'];
|
|
7
|
+
function getPackageRoot() {
|
|
8
|
+
return path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
9
|
+
}
|
|
10
|
+
function getMonorepoRoot(packageRoot = getPackageRoot()) {
|
|
11
|
+
return path.resolve(packageRoot, '../..');
|
|
12
|
+
}
|
|
13
|
+
function getMonorepoSandboxRoot(packageRoot = getPackageRoot()) {
|
|
14
|
+
return path.join(getMonorepoRoot(packageRoot), 'apps/sandbox');
|
|
15
|
+
}
|
|
16
|
+
function isDoistbotMonorepoRoot(monorepoRoot) {
|
|
17
|
+
try {
|
|
18
|
+
const packageJson = JSON.parse(fs.readFileSync(path.join(monorepoRoot, 'package.json'), 'utf8'));
|
|
19
|
+
return packageJson.name === 'doistbot-app';
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
function getSandboxDistPathFromRoot(sandboxRoot, ...segments) {
|
|
26
|
+
return path.join(sandboxRoot, 'dist', ...segments);
|
|
27
|
+
}
|
|
28
|
+
export function getSandboxRoot(packageRoot = getPackageRoot()) {
|
|
29
|
+
const monorepoRoot = getMonorepoRoot(packageRoot);
|
|
30
|
+
const monorepoSandboxRoot = getMonorepoSandboxRoot(packageRoot);
|
|
31
|
+
if (isDoistbotMonorepoRoot(monorepoRoot)) {
|
|
32
|
+
return monorepoSandboxRoot;
|
|
33
|
+
}
|
|
34
|
+
const bundledSandboxRoot = path.join(packageRoot, 'sandbox');
|
|
35
|
+
if (fs.existsSync(getSandboxDistPathFromRoot(bundledSandboxRoot, ...SANDBOX_REVIEW_MODULE_PATH))) {
|
|
36
|
+
return bundledSandboxRoot;
|
|
37
|
+
}
|
|
38
|
+
return monorepoSandboxRoot;
|
|
39
|
+
}
|
|
40
|
+
function getSandboxDistPath(...segments) {
|
|
41
|
+
return getSandboxDistPathFromRoot(getSandboxRoot(), ...segments);
|
|
42
|
+
}
|
|
43
|
+
function getSandboxReviewModulePath() {
|
|
44
|
+
return getSandboxDistPath(...SANDBOX_REVIEW_MODULE_PATH);
|
|
45
|
+
}
|
|
46
|
+
function getSandboxFormatModulePath() {
|
|
47
|
+
return getSandboxDistPath(...SANDBOX_FORMAT_MODULE_PATH);
|
|
48
|
+
}
|
|
49
|
+
async function loadBundledModule(modulePath, missingMessage) {
|
|
50
|
+
if (!modulePath || !fs.existsSync(modulePath)) {
|
|
51
|
+
throw new Error(missingMessage);
|
|
52
|
+
}
|
|
53
|
+
return (await import(pathToFileURL(modulePath).href));
|
|
54
|
+
}
|
|
55
|
+
export async function loadSandboxReviewModule() {
|
|
56
|
+
return loadBundledModule(getSandboxReviewModulePath(), 'Sandbox review runner is missing. Rebuild or reinstall the Doistbot CLI package.');
|
|
57
|
+
}
|
|
58
|
+
export async function loadSandboxFormatModule() {
|
|
59
|
+
return loadBundledModule(getSandboxFormatModulePath(), 'Sandbox review formatter is missing. Rebuild or reinstall the Doistbot CLI package.');
|
|
60
|
+
}
|
|
61
|
+
export async function loadSandboxPrReviewModule() {
|
|
62
|
+
return loadBundledModule(getSandboxDistPath('tasks', 'review', 'pr-dry-run.js'), 'PR dry-run runner is missing. Run `npm run build` in doistbot-app.');
|
|
63
|
+
}
|
|
64
|
+
export function runGit(args, cwd = process.cwd()) {
|
|
65
|
+
return execFileSync('git', args, {
|
|
66
|
+
cwd,
|
|
67
|
+
encoding: 'utf8',
|
|
68
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
export function getRepositoryRoot() {
|
|
72
|
+
return runGit(['rev-parse', '--show-toplevel']).trim();
|
|
73
|
+
}
|
|
74
|
+
export function checkRepository() {
|
|
75
|
+
try {
|
|
76
|
+
const root = getRepositoryRoot();
|
|
77
|
+
return { ok: true, message: root };
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
return { ok: false, message: 'current directory is not inside a git repository' };
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
export function checkGit() {
|
|
84
|
+
try {
|
|
85
|
+
return { ok: true, message: runGit(['--version']).trim() };
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
return { ok: false, message: 'git is not installed or not on PATH' };
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
export function checkSandboxBuild() {
|
|
92
|
+
const modulePath = getSandboxReviewModulePath();
|
|
93
|
+
return fs.existsSync(modulePath)
|
|
94
|
+
? { ok: true, message: 'sandbox review runner is built' }
|
|
95
|
+
: { ok: false, message: 'rebuild or reinstall the Doistbot CLI package' };
|
|
96
|
+
}
|
|
97
|
+
export function checkPiDependency() {
|
|
98
|
+
try {
|
|
99
|
+
import.meta.resolve('@earendil-works/pi-coding-agent');
|
|
100
|
+
return { ok: true, message: 'Pi coding agent is installed' };
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
return { ok: false, message: 'run `npm install` to install Pi dependencies' };
|
|
104
|
+
}
|
|
105
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
const SERVICE_NAME = 'doistbot-cli';
|
|
2
|
+
export class SecureStoreUnavailableError extends Error {
|
|
3
|
+
constructor(message = 'System credential storage is unavailable') {
|
|
4
|
+
super(message);
|
|
5
|
+
this.name = 'SecureStoreUnavailableError';
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
export function getSecureStoreLabel(platform = process.platform) {
|
|
9
|
+
switch (platform) {
|
|
10
|
+
case 'darwin':
|
|
11
|
+
return 'macOS Keychain';
|
|
12
|
+
case 'win32':
|
|
13
|
+
return 'Windows Credential Manager';
|
|
14
|
+
default:
|
|
15
|
+
return 'system keyring';
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
export function describeError(error) {
|
|
19
|
+
return error instanceof Error ? error.message : String(error);
|
|
20
|
+
}
|
|
21
|
+
export function createKeyringStore(account) {
|
|
22
|
+
let cachedUnavailable;
|
|
23
|
+
function rememberUnavailable(error) {
|
|
24
|
+
const wrapped = toUnavailableError(error);
|
|
25
|
+
cachedUnavailable = wrapped;
|
|
26
|
+
return wrapped;
|
|
27
|
+
}
|
|
28
|
+
return {
|
|
29
|
+
async getSecret() {
|
|
30
|
+
if (cachedUnavailable) {
|
|
31
|
+
throw cachedUnavailable;
|
|
32
|
+
}
|
|
33
|
+
const entry = await getEntry(account, rememberUnavailable);
|
|
34
|
+
try {
|
|
35
|
+
const raw = (await entry.getPassword()) ?? null;
|
|
36
|
+
return raw === null ? { value: null } : { value: raw, backend: 'keyring' };
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
throw rememberUnavailable(error);
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
async setSecret(secret) {
|
|
43
|
+
if (cachedUnavailable) {
|
|
44
|
+
throw cachedUnavailable;
|
|
45
|
+
}
|
|
46
|
+
const entry = await getEntry(account, rememberUnavailable);
|
|
47
|
+
try {
|
|
48
|
+
await entry.setPassword(secret);
|
|
49
|
+
return 'keyring';
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
throw rememberUnavailable(error);
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
async deleteSecret() {
|
|
56
|
+
if (cachedUnavailable) {
|
|
57
|
+
throw cachedUnavailable;
|
|
58
|
+
}
|
|
59
|
+
const entry = await getEntry(account, rememberUnavailable);
|
|
60
|
+
try {
|
|
61
|
+
return await entry.deleteCredential();
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
throw rememberUnavailable(error);
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
export function createCompositeStore(keyring, file) {
|
|
70
|
+
return {
|
|
71
|
+
async getSecret() {
|
|
72
|
+
try {
|
|
73
|
+
const fromKeyring = await keyring.getSecret();
|
|
74
|
+
if (fromKeyring.value !== null) {
|
|
75
|
+
return fromKeyring;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
if (!(error instanceof SecureStoreUnavailableError)) {
|
|
80
|
+
throw error;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return await file.getSecret();
|
|
84
|
+
},
|
|
85
|
+
async setSecret(secret) {
|
|
86
|
+
try {
|
|
87
|
+
const backend = await keyring.setSecret(secret);
|
|
88
|
+
await file.deleteSecret().catch(() => undefined);
|
|
89
|
+
return backend;
|
|
90
|
+
}
|
|
91
|
+
catch (keyringError) {
|
|
92
|
+
if (!(keyringError instanceof SecureStoreUnavailableError)) {
|
|
93
|
+
throw keyringError;
|
|
94
|
+
}
|
|
95
|
+
try {
|
|
96
|
+
return await file.setSecret(secret);
|
|
97
|
+
}
|
|
98
|
+
catch (fileError) {
|
|
99
|
+
throw combineFailure(keyringError, fileError);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
async deleteSecret() {
|
|
104
|
+
let deleted = false;
|
|
105
|
+
let keyringFailure;
|
|
106
|
+
try {
|
|
107
|
+
deleted = (await keyring.deleteSecret()) || deleted;
|
|
108
|
+
}
|
|
109
|
+
catch (error) {
|
|
110
|
+
if (!(error instanceof SecureStoreUnavailableError)) {
|
|
111
|
+
throw error;
|
|
112
|
+
}
|
|
113
|
+
keyringFailure = error;
|
|
114
|
+
}
|
|
115
|
+
try {
|
|
116
|
+
deleted = (await file.deleteSecret()) || deleted;
|
|
117
|
+
}
|
|
118
|
+
catch (fileError) {
|
|
119
|
+
if (keyringFailure) {
|
|
120
|
+
throw combineFailure(keyringFailure, fileError);
|
|
121
|
+
}
|
|
122
|
+
throw fileError;
|
|
123
|
+
}
|
|
124
|
+
if (keyringFailure) {
|
|
125
|
+
throw new SecureStoreUnavailableError(`Failed to delete from system keyring (${keyringFailure.message}). File store deletion succeeded but the keyring entry may still exist.`);
|
|
126
|
+
}
|
|
127
|
+
return deleted;
|
|
128
|
+
},
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
async function getEntry(account, rememberUnavailable) {
|
|
132
|
+
try {
|
|
133
|
+
const { AsyncEntry } = await import('@napi-rs/keyring');
|
|
134
|
+
return new AsyncEntry(SERVICE_NAME, account);
|
|
135
|
+
}
|
|
136
|
+
catch (error) {
|
|
137
|
+
throw rememberUnavailable(error);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
function toUnavailableError(error) {
|
|
141
|
+
if (error instanceof SecureStoreUnavailableError) {
|
|
142
|
+
return error;
|
|
143
|
+
}
|
|
144
|
+
return new SecureStoreUnavailableError(describeError(error));
|
|
145
|
+
}
|
|
146
|
+
function combineFailure(keyringError, fileError) {
|
|
147
|
+
return new SecureStoreUnavailableError(`System keyring unavailable (${keyringError.message}) and file fallback failed (${describeError(fileError)}).`);
|
|
148
|
+
}
|
package/dist/terminal.js
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import yoctoSpinner from 'yocto-spinner';
|
|
3
|
+
export function isInteractiveOutputAllowed({ env = process.env, stream = process.stderr, } = {}) {
|
|
4
|
+
return env.CI !== 'true' && env.CI !== '1' && stream.isTTY === true;
|
|
5
|
+
}
|
|
6
|
+
export function writeLine(message = '') {
|
|
7
|
+
process.stdout.write(`${message}\n`);
|
|
8
|
+
}
|
|
9
|
+
export function writeErrorLine(message) {
|
|
10
|
+
process.stderr.write(`${message}\n`);
|
|
11
|
+
}
|
|
12
|
+
function getDefaultTerminalConfig() {
|
|
13
|
+
return {
|
|
14
|
+
supportsColor: process.env.NO_COLOR === undefined &&
|
|
15
|
+
process.env.TERM !== 'dumb' &&
|
|
16
|
+
process.stdout.isTTY === true &&
|
|
17
|
+
chalk.level > 0,
|
|
18
|
+
isInteractive: isInteractiveOutputAllowed(),
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
function colorize(value, style, terminalConfig) {
|
|
22
|
+
return terminalConfig.supportsColor ? style(value) : value;
|
|
23
|
+
}
|
|
24
|
+
export function renderAlert(input, terminalConfig = getDefaultTerminalConfig()) {
|
|
25
|
+
if (!terminalConfig.supportsColor) {
|
|
26
|
+
return input;
|
|
27
|
+
}
|
|
28
|
+
return input
|
|
29
|
+
.split(/\r?\n/)
|
|
30
|
+
.map((line) => chalk.bgRed(chalk.white.bold(line.replace(/`([^`]+)`/g, (_match, code) => chalk.whiteBright.inverse(` ${code} `)))))
|
|
31
|
+
.join('\n');
|
|
32
|
+
}
|
|
33
|
+
function formatStatus(message, terminalConfig) {
|
|
34
|
+
return colorize(message, chalk.blue, terminalConfig);
|
|
35
|
+
}
|
|
36
|
+
function formatDiagnosticText(label, result, terminalConfig) {
|
|
37
|
+
const title = colorize(label.padEnd(14), chalk.bold, terminalConfig);
|
|
38
|
+
const message = result.ok
|
|
39
|
+
? colorize(result.message, chalk.dim, terminalConfig)
|
|
40
|
+
: colorize(result.message, chalk.red, terminalConfig);
|
|
41
|
+
return `${title} ${message}`;
|
|
42
|
+
}
|
|
43
|
+
function getDiagnosticSymbol(result, terminalConfig) {
|
|
44
|
+
if (result.ok) {
|
|
45
|
+
return colorize('✓', chalk.green, terminalConfig);
|
|
46
|
+
}
|
|
47
|
+
return colorize('✗', chalk.red, terminalConfig);
|
|
48
|
+
}
|
|
49
|
+
export async function renderDiagnosticCheck(label, check, terminalConfig = getDefaultTerminalConfig()) {
|
|
50
|
+
const result = await check();
|
|
51
|
+
process.stdout.write(`${getDiagnosticSymbol(result, terminalConfig)} ${formatDiagnosticText(label, result, terminalConfig)}\n`);
|
|
52
|
+
return result;
|
|
53
|
+
}
|
|
54
|
+
export function renderMarkdown(input, terminalConfig = getDefaultTerminalConfig()) {
|
|
55
|
+
const lines = input.trim().split(/\r?\n/);
|
|
56
|
+
const rendered = [];
|
|
57
|
+
let inCodeBlock = false;
|
|
58
|
+
for (const rawLine of lines) {
|
|
59
|
+
const line = rawLine.trimEnd();
|
|
60
|
+
if (line.startsWith('```')) {
|
|
61
|
+
inCodeBlock = !inCodeBlock;
|
|
62
|
+
if (!inCodeBlock) {
|
|
63
|
+
rendered.push('');
|
|
64
|
+
}
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
if (inCodeBlock) {
|
|
68
|
+
rendered.push(colorize(` ${line}`, chalk.cyan, terminalConfig));
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
const heading = line.match(/^(#{1,6})\s+(.*)$/);
|
|
72
|
+
if (heading?.[2]) {
|
|
73
|
+
rendered.push(colorize(heading[2], chalk.bold.cyan, terminalConfig));
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
if (/^---+$/.test(line)) {
|
|
77
|
+
rendered.push(colorize('---', chalk.dim, terminalConfig));
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
if (line === 'Inline review comments:') {
|
|
81
|
+
rendered.push(colorize(line, chalk.bold.magenta, terminalConfig));
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
if (/^Comment \d+: .*$/.test(line)) {
|
|
85
|
+
rendered.push(colorize(line, chalk.bold.yellow, terminalConfig));
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
rendered.push(renderInlineCode(renderPriority(line, terminalConfig), terminalConfig));
|
|
89
|
+
}
|
|
90
|
+
return rendered.join('\n').trim();
|
|
91
|
+
}
|
|
92
|
+
export async function withSpinner(message, task, terminalConfig = getDefaultTerminalConfig()) {
|
|
93
|
+
if (!terminalConfig.isInteractive) {
|
|
94
|
+
return await task({ enabled: false, update() { } });
|
|
95
|
+
}
|
|
96
|
+
let currentMessage = message;
|
|
97
|
+
const spinner = yoctoSpinner({ text: formatStatus(currentMessage, terminalConfig) });
|
|
98
|
+
spinner.start();
|
|
99
|
+
const status = {
|
|
100
|
+
enabled: true,
|
|
101
|
+
update(nextMessage) {
|
|
102
|
+
if (!nextMessage.trim() || nextMessage === currentMessage) {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
currentMessage = nextMessage;
|
|
106
|
+
spinner.text = formatStatus(currentMessage, terminalConfig);
|
|
107
|
+
},
|
|
108
|
+
};
|
|
109
|
+
try {
|
|
110
|
+
return await task(status);
|
|
111
|
+
}
|
|
112
|
+
finally {
|
|
113
|
+
spinner.stop();
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
const PROVIDER_DISPLAY_NAMES = {
|
|
117
|
+
deepseek: 'DeepSeek',
|
|
118
|
+
gemini: 'Gemini',
|
|
119
|
+
grok: 'Grok',
|
|
120
|
+
zai: 'Z.ai',
|
|
121
|
+
openai: 'OpenAI',
|
|
122
|
+
};
|
|
123
|
+
function pluralizeChangedFiles(fileCount) {
|
|
124
|
+
return `${fileCount} changed file${fileCount === 1 ? '' : 's'}`;
|
|
125
|
+
}
|
|
126
|
+
export function formatReviewProgress(event) {
|
|
127
|
+
switch (event.type) {
|
|
128
|
+
case 'local_context':
|
|
129
|
+
return 'Resolving repository metadata and local diff';
|
|
130
|
+
case 'local_files_resolved':
|
|
131
|
+
return `Found ${pluralizeChangedFiles(event.fileCount)} to review`;
|
|
132
|
+
case 'prompt_prepared':
|
|
133
|
+
return `Prepared ${pluralizeChangedFiles(event.fileCount)} for review`;
|
|
134
|
+
case 'review_mode':
|
|
135
|
+
return 'Assessing review mode…';
|
|
136
|
+
case 'formatting_results':
|
|
137
|
+
return 'Review complete. Formatting results';
|
|
138
|
+
case 'agent_start':
|
|
139
|
+
return `${PROVIDER_DISPLAY_NAMES[event.provider]} started reviewing the diff`;
|
|
140
|
+
case 'agent_end':
|
|
141
|
+
return `${PROVIDER_DISPLAY_NAMES[event.provider]} finished its review pass`;
|
|
142
|
+
case 'thinking':
|
|
143
|
+
return `${PROVIDER_DISPLAY_NAMES[event.provider]} is reasoning through the changes`;
|
|
144
|
+
case 'responding':
|
|
145
|
+
return `${PROVIDER_DISPLAY_NAMES[event.provider]} is writing review findings`;
|
|
146
|
+
case 'limit_reached': {
|
|
147
|
+
const limit = event.level === 'soft' ? 'inspection budget' : 'inspection limit';
|
|
148
|
+
return `${PROVIDER_DISPLAY_NAMES[event.provider]} reached its ${limit} and is finalizing the review`;
|
|
149
|
+
}
|
|
150
|
+
case 'tool_start':
|
|
151
|
+
return event.toolName
|
|
152
|
+
? `${PROVIDER_DISPLAY_NAMES[event.provider]} is inspecting the repo with ${event.toolName}`
|
|
153
|
+
: `${PROVIDER_DISPLAY_NAMES[event.provider]} is inspecting the repo`;
|
|
154
|
+
case 'tool_update':
|
|
155
|
+
return event.toolName
|
|
156
|
+
? `${PROVIDER_DISPLAY_NAMES[event.provider]} is still running ${event.toolName}`
|
|
157
|
+
: `${PROVIDER_DISPLAY_NAMES[event.provider]} is still inspecting the repo`;
|
|
158
|
+
case 'tool_end':
|
|
159
|
+
return event.toolName
|
|
160
|
+
? `${PROVIDER_DISPLAY_NAMES[event.provider]} finished ${event.toolName}`
|
|
161
|
+
: `${PROVIDER_DISPLAY_NAMES[event.provider]} finished a repo inspection step`;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
function renderInlineCode(line, terminalConfig) {
|
|
165
|
+
if (!terminalConfig.supportsColor) {
|
|
166
|
+
return line;
|
|
167
|
+
}
|
|
168
|
+
return line.replace(/`([^`]+)`/g, (_match, code) => chalk.cyan(code));
|
|
169
|
+
}
|
|
170
|
+
function renderPriority(line, terminalConfig) {
|
|
171
|
+
if (!terminalConfig.supportsColor) {
|
|
172
|
+
return line;
|
|
173
|
+
}
|
|
174
|
+
return line.replace(/^\[(P[0-3])\]/, (_match, priority) => {
|
|
175
|
+
switch (priority) {
|
|
176
|
+
case 'P0':
|
|
177
|
+
case 'P1':
|
|
178
|
+
return chalk.bold.red(`[${priority}]`);
|
|
179
|
+
case 'P2':
|
|
180
|
+
return chalk.bold.yellow(`[${priority}]`);
|
|
181
|
+
default:
|
|
182
|
+
return chalk.bold.cyan(`[${priority}]`);
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { vi } from 'vitest';
|
|
2
|
+
import { SecureStoreUnavailableError, } from '../secure-store.js';
|
|
3
|
+
export function createMemoryStore({ initial = null, backend = 'keyring', } = {}) {
|
|
4
|
+
let secret = initial;
|
|
5
|
+
return {
|
|
6
|
+
getSecret: vi.fn(async () => secret === null ? { value: null } : { value: secret, backend }),
|
|
7
|
+
setSecret: vi.fn(async (value) => {
|
|
8
|
+
secret = value;
|
|
9
|
+
return backend;
|
|
10
|
+
}),
|
|
11
|
+
deleteSecret: vi.fn(async () => {
|
|
12
|
+
if (secret === null) {
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
secret = null;
|
|
16
|
+
return true;
|
|
17
|
+
}),
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
export function createUnavailableStore(message = 'keyring offline') {
|
|
21
|
+
async function fail() {
|
|
22
|
+
throw new SecureStoreUnavailableError(message);
|
|
23
|
+
}
|
|
24
|
+
return {
|
|
25
|
+
getSecret: vi.fn(fail),
|
|
26
|
+
setSecret: vi.fn(fail),
|
|
27
|
+
deleteSecret: vi.fn(fail),
|
|
28
|
+
};
|
|
29
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { parseArgs } from 'node:util';
|
|
3
|
+
import { refreshCliUpdateCache } from './update-check.js';
|
|
4
|
+
const { values } = parseArgs({
|
|
5
|
+
options: {
|
|
6
|
+
'cache-path': { type: 'string' },
|
|
7
|
+
'current-version': { type: 'string' },
|
|
8
|
+
},
|
|
9
|
+
});
|
|
10
|
+
if (!values['current-version']) {
|
|
11
|
+
throw new Error('Missing --current-version');
|
|
12
|
+
}
|
|
13
|
+
await refreshCliUpdateCache({
|
|
14
|
+
cachePath: values['cache-path'],
|
|
15
|
+
currentVersion: values['current-version'],
|
|
16
|
+
});
|