@cyanautomation/kaseki-agent 1.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.dockerignore +54 -0
- package/.eslintignore +11 -0
- package/.eslintrc.json +95 -0
- package/.github/ISSUE_TEMPLATE/bug_report.md +53 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +53 -0
- package/.github/ISSUE_TEMPLATE/security.md +51 -0
- package/.github/PULL_REQUEST_TEMPLATE/default.md +71 -0
- package/.github/dependabot.yml +38 -0
- package/.github/skills/dependency-cache-optimization/SKILL.md +526 -0
- package/.github/skills/docker-image-management/SKILL.md +532 -0
- package/.github/skills/frontend-design/SKILL.md +782 -0
- package/.github/skills/prompt-engineering/SKILL.md +360 -0
- package/.github/skills/quality-gate-config/SKILL.md +591 -0
- package/.github/skills/result-report-analysis/SKILL.md +576 -0
- package/.github/skills/test-automation/SKILL.md +593 -0
- package/.github/skills/workflow-diagnosis/SKILL.md +468 -0
- package/.github/workflows/build-docker-image.yml +453 -0
- package/.github/workflows/release.yml +68 -0
- package/.releaserc.json +135 -0
- package/CHANGELOG.md +117 -0
- package/CLAUDE.md +336 -0
- package/CONTRIBUTING.md +339 -0
- package/Dockerfile +217 -0
- package/README.md +1527 -0
- package/STYLE.md +521 -0
- package/add-js-extensions.d.ts +9 -0
- package/add-js-extensions.d.ts.map +1 -0
- package/add-js-extensions.js.map +1 -0
- package/dist/add-js-extensions.d.ts +9 -0
- package/dist/add-js-extensions.d.ts.map +1 -0
- package/dist/add-js-extensions.js +52 -0
- package/dist/add-js-extensions.js.map +1 -0
- package/dist/ansi-colors.d.ts +26 -0
- package/dist/ansi-colors.d.ts.map +1 -0
- package/dist/ansi-colors.js +51 -0
- package/dist/ansi-colors.js.map +1 -0
- package/dist/cli/BaseCommand.d.ts +18 -0
- package/dist/cli/BaseCommand.d.ts.map +1 -0
- package/dist/cli/BaseCommand.js +31 -0
- package/dist/cli/BaseCommand.js.map +1 -0
- package/dist/cli/KasekiCLI.d.ts +30 -0
- package/dist/cli/KasekiCLI.d.ts.map +1 -0
- package/dist/cli/KasekiCLI.js +134 -0
- package/dist/cli/KasekiCLI.js.map +1 -0
- package/dist/cli/commands/ConfigCommand.d.ts +13 -0
- package/dist/cli/commands/ConfigCommand.d.ts.map +1 -0
- package/dist/cli/commands/ConfigCommand.js +131 -0
- package/dist/cli/commands/ConfigCommand.js.map +1 -0
- package/dist/cli/commands/DoctorCommand.d.ts +45 -0
- package/dist/cli/commands/DoctorCommand.d.ts.map +1 -0
- package/dist/cli/commands/DoctorCommand.js +309 -0
- package/dist/cli/commands/DoctorCommand.js.map +1 -0
- package/dist/cli/commands/ListCommand.d.ts +9 -0
- package/dist/cli/commands/ListCommand.d.ts.map +1 -0
- package/dist/cli/commands/ListCommand.js +81 -0
- package/dist/cli/commands/ListCommand.js.map +1 -0
- package/dist/cli/commands/ReportCommand.d.ts +9 -0
- package/dist/cli/commands/ReportCommand.d.ts.map +1 -0
- package/dist/cli/commands/ReportCommand.js +98 -0
- package/dist/cli/commands/ReportCommand.js.map +1 -0
- package/dist/cli/commands/RunCommand.d.ts +13 -0
- package/dist/cli/commands/RunCommand.d.ts.map +1 -0
- package/dist/cli/commands/RunCommand.js +191 -0
- package/dist/cli/commands/RunCommand.js.map +1 -0
- package/dist/cli/commands/SecretsCommand.d.ts +9 -0
- package/dist/cli/commands/SecretsCommand.d.ts.map +1 -0
- package/dist/cli/commands/SecretsCommand.js +109 -0
- package/dist/cli/commands/SecretsCommand.js.map +1 -0
- package/dist/cli/commands/ServeCommand.d.ts +9 -0
- package/dist/cli/commands/ServeCommand.d.ts.map +1 -0
- package/dist/cli/commands/ServeCommand.js +50 -0
- package/dist/cli/commands/ServeCommand.js.map +1 -0
- package/dist/cli/commands/SetupCommand.d.ts +42 -0
- package/dist/cli/commands/SetupCommand.d.ts.map +1 -0
- package/dist/cli/commands/SetupCommand.js +249 -0
- package/dist/cli/commands/SetupCommand.js.map +1 -0
- package/dist/cli.d.ts +9 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +130 -0
- package/dist/cli.js.map +1 -0
- package/dist/config/ConfigManager.d.ts +395 -0
- package/dist/config/ConfigManager.d.ts.map +1 -0
- package/dist/config/ConfigManager.js +446 -0
- package/dist/config/ConfigManager.js.map +1 -0
- package/dist/docker/DockerManager.d.ts +69 -0
- package/dist/docker/DockerManager.d.ts.map +1 -0
- package/dist/docker/DockerManager.js +266 -0
- package/dist/docker/DockerManager.js.map +1 -0
- package/dist/event-aggregator.d.ts +71 -0
- package/dist/event-aggregator.d.ts.map +1 -0
- package/dist/event-aggregator.js +95 -0
- package/dist/event-aggregator.js.map +1 -0
- package/dist/github-app-token.d.ts +16 -0
- package/dist/github-app-token.d.ts.map +1 -0
- package/dist/github-app-token.js +148 -0
- package/dist/github-app-token.js.map +1 -0
- package/dist/idempotency-store.d.ts +61 -0
- package/dist/idempotency-store.d.ts.map +1 -0
- package/dist/idempotency-store.js +321 -0
- package/dist/idempotency-store.js.map +1 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +31 -0
- package/dist/index.js.map +1 -0
- package/dist/instance/InstanceManager.d.ts +81 -0
- package/dist/instance/InstanceManager.d.ts.map +1 -0
- package/dist/instance/InstanceManager.js +220 -0
- package/dist/instance/InstanceManager.js.map +1 -0
- package/dist/instance-metadata-reader.d.ts +48 -0
- package/dist/instance-metadata-reader.d.ts.map +1 -0
- package/dist/instance-metadata-reader.js +94 -0
- package/dist/instance-metadata-reader.js.map +1 -0
- package/dist/instance-state-derivation.d.ts +42 -0
- package/dist/instance-state-derivation.d.ts.map +1 -0
- package/dist/instance-state-derivation.js +133 -0
- package/dist/instance-state-derivation.js.map +1 -0
- package/dist/job-scheduler.d.ts +124 -0
- package/dist/job-scheduler.d.ts.map +1 -0
- package/dist/job-scheduler.js +992 -0
- package/dist/job-scheduler.js.map +1 -0
- package/dist/kaseki-api-client.d.ts +89 -0
- package/dist/kaseki-api-client.d.ts.map +1 -0
- package/dist/kaseki-api-client.js +405 -0
- package/dist/kaseki-api-client.js.map +1 -0
- package/dist/kaseki-api-config.d.ts +34 -0
- package/dist/kaseki-api-config.d.ts.map +1 -0
- package/dist/kaseki-api-config.js +113 -0
- package/dist/kaseki-api-config.js.map +1 -0
- package/dist/kaseki-api-routes.d.ts +13 -0
- package/dist/kaseki-api-routes.d.ts.map +1 -0
- package/dist/kaseki-api-routes.js +559 -0
- package/dist/kaseki-api-routes.js.map +1 -0
- package/dist/kaseki-api-service-wrapper.d.ts +43 -0
- package/dist/kaseki-api-service-wrapper.d.ts.map +1 -0
- package/dist/kaseki-api-service-wrapper.js +150 -0
- package/dist/kaseki-api-service-wrapper.js.map +1 -0
- package/dist/kaseki-api-service.d.ts +16 -0
- package/dist/kaseki-api-service.d.ts.map +1 -0
- package/dist/kaseki-api-service.js +143 -0
- package/dist/kaseki-api-service.js.map +1 -0
- package/dist/kaseki-api-types.d.ts +440 -0
- package/dist/kaseki-api-types.d.ts.map +1 -0
- package/dist/kaseki-api-types.js +64 -0
- package/dist/kaseki-api-types.js.map +1 -0
- package/dist/kaseki-cli-lib.d.ts +219 -0
- package/dist/kaseki-cli-lib.d.ts.map +1 -0
- package/dist/kaseki-cli-lib.js +523 -0
- package/dist/kaseki-cli-lib.js.map +1 -0
- package/dist/kaseki-cli.d.ts +38 -0
- package/dist/kaseki-cli.d.ts.map +1 -0
- package/dist/kaseki-cli.js +559 -0
- package/dist/kaseki-cli.js.map +1 -0
- package/dist/kaseki-report.d.ts +3 -0
- package/dist/kaseki-report.d.ts.map +1 -0
- package/dist/kaseki-report.js +140 -0
- package/dist/kaseki-report.js.map +1 -0
- package/dist/lib/subprocess-helpers.d.ts +98 -0
- package/dist/lib/subprocess-helpers.d.ts.map +1 -0
- package/dist/lib/subprocess-helpers.js +136 -0
- package/dist/lib/subprocess-helpers.js.map +1 -0
- package/dist/logger.d.ts +39 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +79 -0
- package/dist/logger.js.map +1 -0
- package/dist/metrics.d.ts +19 -0
- package/dist/metrics.d.ts.map +1 -0
- package/dist/metrics.js +59 -0
- package/dist/metrics.js.map +1 -0
- package/dist/middleware/job-lookup.d.ts +27 -0
- package/dist/middleware/job-lookup.d.ts.map +1 -0
- package/dist/middleware/job-lookup.js +28 -0
- package/dist/middleware/job-lookup.js.map +1 -0
- package/dist/pi-event-filter.d.ts +3 -0
- package/dist/pi-event-filter.d.ts.map +1 -0
- package/dist/pi-event-filter.js +126 -0
- package/dist/pi-event-filter.js.map +1 -0
- package/dist/pi-progress-stream.d.ts +3 -0
- package/dist/pi-progress-stream.d.ts.map +1 -0
- package/dist/pi-progress-stream.js +205 -0
- package/dist/pi-progress-stream.js.map +1 -0
- package/dist/pi-progress-summarizer.d.ts +61 -0
- package/dist/pi-progress-summarizer.d.ts.map +1 -0
- package/dist/pi-progress-summarizer.js +246 -0
- package/dist/pi-progress-summarizer.js.map +1 -0
- package/dist/pre-flight-validator.d.ts +72 -0
- package/dist/pre-flight-validator.d.ts.map +1 -0
- package/dist/pre-flight-validator.js +513 -0
- package/dist/pre-flight-validator.js.map +1 -0
- package/dist/progress-stream-utils.d.ts +3 -0
- package/dist/progress-stream-utils.d.ts.map +1 -0
- package/dist/progress-stream-utils.js +15 -0
- package/dist/progress-stream-utils.js.map +1 -0
- package/dist/result-cache.d.ts +52 -0
- package/dist/result-cache.d.ts.map +1 -0
- package/dist/result-cache.js +134 -0
- package/dist/result-cache.js.map +1 -0
- package/dist/routes/artifact-routes.d.ts +10 -0
- package/dist/routes/artifact-routes.d.ts.map +1 -0
- package/dist/routes/artifact-routes.js +126 -0
- package/dist/routes/artifact-routes.js.map +1 -0
- package/dist/routes/log-routes.d.ts +8 -0
- package/dist/routes/log-routes.d.ts.map +1 -0
- package/dist/routes/log-routes.js +345 -0
- package/dist/routes/log-routes.js.map +1 -0
- package/dist/routes/status-routes.d.ts +8 -0
- package/dist/routes/status-routes.d.ts.map +1 -0
- package/dist/routes/status-routes.js +82 -0
- package/dist/routes/status-routes.js.map +1 -0
- package/dist/routes/webhook-routes.d.ts +6 -0
- package/dist/routes/webhook-routes.d.ts.map +1 -0
- package/dist/routes/webhook-routes.js +86 -0
- package/dist/routes/webhook-routes.js.map +1 -0
- package/dist/run-artifact-metadata-cache.d.ts +42 -0
- package/dist/run-artifact-metadata-cache.d.ts.map +1 -0
- package/dist/run-artifact-metadata-cache.js +139 -0
- package/dist/run-artifact-metadata-cache.js.map +1 -0
- package/dist/secret-value-cache.d.ts +13 -0
- package/dist/secret-value-cache.d.ts.map +1 -0
- package/dist/secret-value-cache.js +44 -0
- package/dist/secret-value-cache.js.map +1 -0
- package/dist/secrets/SecretsManager.d.ts +80 -0
- package/dist/secrets/SecretsManager.d.ts.map +1 -0
- package/dist/secrets/SecretsManager.js +306 -0
- package/dist/secrets/SecretsManager.js.map +1 -0
- package/dist/test-utils.d.ts +55 -0
- package/dist/test-utils.d.ts.map +1 -0
- package/dist/test-utils.js +48 -0
- package/dist/test-utils.js.map +1 -0
- package/dist/timestamp-tracker.d.ts +75 -0
- package/dist/timestamp-tracker.d.ts.map +1 -0
- package/dist/timestamp-tracker.js +121 -0
- package/dist/timestamp-tracker.js.map +1 -0
- package/dist/utils/failure-artifact-writer.d.ts +29 -0
- package/dist/utils/failure-artifact-writer.d.ts.map +1 -0
- package/dist/utils/failure-artifact-writer.js +157 -0
- package/dist/utils/failure-artifact-writer.js.map +1 -0
- package/dist/utils/file-helpers.d.ts +41 -0
- package/dist/utils/file-helpers.d.ts.map +1 -0
- package/dist/utils/file-helpers.js +143 -0
- package/dist/utils/file-helpers.js.map +1 -0
- package/dist/utils/http-client-factory.d.ts +46 -0
- package/dist/utils/http-client-factory.d.ts.map +1 -0
- package/dist/utils/http-client-factory.js +114 -0
- package/dist/utils/http-client-factory.js.map +1 -0
- package/dist/utils/progress-normalizer.d.ts +13 -0
- package/dist/utils/progress-normalizer.d.ts.map +1 -0
- package/dist/utils/progress-normalizer.js +57 -0
- package/dist/utils/progress-normalizer.js.map +1 -0
- package/dist/utils/response-helpers.d.ts +34 -0
- package/dist/utils/response-helpers.d.ts.map +1 -0
- package/dist/utils/response-helpers.js +78 -0
- package/dist/utils/response-helpers.js.map +1 -0
- package/dist/utils/route-helpers.d.ts +17 -0
- package/dist/utils/route-helpers.d.ts.map +1 -0
- package/dist/utils/route-helpers.js +22 -0
- package/dist/utils/route-helpers.js.map +1 -0
- package/dist/utils/status-response-builder.d.ts +23 -0
- package/dist/utils/status-response-builder.d.ts.map +1 -0
- package/dist/utils/status-response-builder.js +144 -0
- package/dist/utils/status-response-builder.js.map +1 -0
- package/dist/utils/type-guards.d.ts +37 -0
- package/dist/utils/type-guards.d.ts.map +1 -0
- package/dist/utils/type-guards.js +45 -0
- package/dist/utils/type-guards.js.map +1 -0
- package/dist/utils/utf8-helpers.d.ts +32 -0
- package/dist/utils/utf8-helpers.d.ts.map +1 -0
- package/dist/utils/utf8-helpers.js +97 -0
- package/dist/utils/utf8-helpers.js.map +1 -0
- package/dist/utils/webhook-event-builder.d.ts +26 -0
- package/dist/utils/webhook-event-builder.d.ts.map +1 -0
- package/dist/utils/webhook-event-builder.js +77 -0
- package/dist/utils/webhook-event-builder.js.map +1 -0
- package/dist/webhook-manager.d.ts +56 -0
- package/dist/webhook-manager.d.ts.map +1 -0
- package/dist/webhook-manager.js +359 -0
- package/dist/webhook-manager.js.map +1 -0
- package/docker/workspace-cache/package-lock.json +13 -0
- package/docker/workspace-cache/package.json +7 -0
- package/docker-compose.yml +53 -0
- package/docs/API.md +708 -0
- package/docs/BACKLOG.md +19 -0
- package/docs/BUILD_STRATEGY.md +404 -0
- package/docs/CLI.md +569 -0
- package/docs/DEPLOYMENT.md +521 -0
- package/docs/DEVELOPMENT.md +459 -0
- package/docs/DOCKER_SETUP.md +522 -0
- package/docs/ENHANCED_PROGRESS_LOGS.md +264 -0
- package/docs/IMPLEMENTATION_SUMMARY.md +549 -0
- package/docs/INTEGRATION_EXAMPLE.md +217 -0
- package/docs/NPM_SETUP.md +468 -0
- package/docs/PHASE1-4_IMPLEMENTATION.md +302 -0
- package/docs/PHASE1_COMPLETION.md +192 -0
- package/docs/PHASE2_COMPLETION.md +134 -0
- package/docs/PHASE6_MIGRATION.md +392 -0
- package/docs/PRINTF_SAFETY_FIX.md +282 -0
- package/docs/QUALITY_GATES.md +369 -0
- package/docs/SETUP_GUIDE.md +482 -0
- package/docs/TASK_PROMPT_TEMPLATES.md +533 -0
- package/docs/VALIDATION_FIX.md +139 -0
- package/docs/VERIFICATION_CHECKLIST.md +335 -0
- package/docs/repo-maturity.md +760 -0
- package/fix-tests.d.ts +9 -0
- package/fix-tests.d.ts.map +1 -0
- package/fix-tests.js.map +1 -0
- package/fix-tests.ts +53 -0
- package/jest.config.ts +31 -0
- package/kaseki +183 -0
- package/kaseki-agent.sh +1961 -0
- package/ops/logrotate/kaseki +10 -0
- package/package.json +83 -0
- package/perf/README.md +54 -0
- package/perf/pi-event-filter.benchmark.test.ts +98 -0
- package/run-kaseki-json.test.sh +106 -0
- package/run-kaseki.sh +990 -0
- package/scripts/allowlist-helper.sh +56 -0
- package/scripts/cleanup-kaseki.sh +168 -0
- package/scripts/deploy-pi-template.sh +293 -0
- package/scripts/docker-entrypoint.sh +71 -0
- package/scripts/dry-run-allowlist.sh +161 -0
- package/scripts/kaseki-activate.sh +396 -0
- package/scripts/kaseki-api.service +62 -0
- package/scripts/kaseki-container-entrypoint-wrapper.sh +119 -0
- package/scripts/kaseki-container-setup-remote.sh +172 -0
- package/scripts/kaseki-container-setup.sh +193 -0
- package/scripts/kaseki-healthcheck.sh +95 -0
- package/scripts/kaseki-install.sh +50 -0
- package/scripts/kaseki-maturity-score.sh +291 -0
- package/scripts/kaseki-performance-metrics.sh +122 -0
- package/scripts/kaseki-preflight.sh +270 -0
- package/scripts/kaseki-setup.sh +265 -0
- package/scripts/pi-setup-remote.sh +213 -0
- package/scripts/setup-github-labels.sh +42 -0
- package/scripts/suggest-allowlist.sh +68 -0
- package/scripts/templates/MULTI_HOST_DISTRIBUTED.md +337 -0
- package/scripts/templates/REST_API_SERVICE.md +490 -0
- package/scripts/templates/SINGLE_HOST_CLI.md +194 -0
- package/scripts/test-github-app.sh +248 -0
- package/src/add-js-extensions.ts +61 -0
- package/src/ansi-colors.test.ts +62 -0
- package/src/ansi-colors.ts +67 -0
- package/src/cli/BaseCommand.ts +40 -0
- package/src/cli/KasekiCLI.ts +154 -0
- package/src/cli/commands/ConfigCommand.ts +145 -0
- package/src/cli/commands/DoctorCommand.ts +329 -0
- package/src/cli/commands/ListCommand.ts +105 -0
- package/src/cli/commands/ReportCommand.ts +110 -0
- package/src/cli/commands/RunCommand.ts +218 -0
- package/src/cli/commands/SecretsCommand.ts +120 -0
- package/src/cli/commands/ServeCommand.ts +62 -0
- package/src/cli/commands/SetupCommand.ts +301 -0
- package/src/cli.ts +138 -0
- package/src/config/ConfigManager.ts +476 -0
- package/src/docker/DockerManager.ts +319 -0
- package/src/docker-entrypoint-packaging.test.ts +33 -0
- package/src/event-aggregator.test.ts +117 -0
- package/src/event-aggregator.ts +126 -0
- package/src/github-app-token.ts +215 -0
- package/src/idempotency-store.test.ts +117 -0
- package/src/idempotency-store.ts +385 -0
- package/src/index.ts +89 -0
- package/src/instance/InstanceManager.ts +285 -0
- package/src/instance-metadata-reader.test.ts +190 -0
- package/src/instance-metadata-reader.ts +129 -0
- package/src/instance-state-derivation.test.ts +263 -0
- package/src/instance-state-derivation.ts +148 -0
- package/src/job-scheduler.test.ts +1236 -0
- package/src/job-scheduler.ts +1117 -0
- package/src/kaseki-api-client.ts +488 -0
- package/src/kaseki-api-config.test.ts +315 -0
- package/src/kaseki-api-config.ts +175 -0
- package/src/kaseki-api-routes.test.ts +1615 -0
- package/src/kaseki-api-routes.ts +643 -0
- package/src/kaseki-api-service-wrapper.ts +188 -0
- package/src/kaseki-api-service.test.ts +418 -0
- package/src/kaseki-api-service.ts +192 -0
- package/src/kaseki-api-types.ts +320 -0
- package/src/kaseki-cli-lib.test.ts +552 -0
- package/src/kaseki-cli-lib.ts +760 -0
- package/src/kaseki-cli.ts +682 -0
- package/src/kaseki-report.test.ts +118 -0
- package/src/kaseki-report.ts +192 -0
- package/src/lib/subprocess-helpers.ts +177 -0
- package/src/logger.ts +114 -0
- package/src/metrics.ts +66 -0
- package/src/middleware/job-lookup.test.ts +113 -0
- package/src/middleware/job-lookup.ts +45 -0
- package/src/pi-event-filter.test.ts +183 -0
- package/src/pi-event-filter.ts +183 -0
- package/src/pi-progress-stream.ts +287 -0
- package/src/pi-progress-summarizer.test.ts +302 -0
- package/src/pi-progress-summarizer.ts +287 -0
- package/src/pre-flight-validator.test.ts +512 -0
- package/src/pre-flight-validator.ts +618 -0
- package/src/progress-stream-utils.test.ts +35 -0
- package/src/progress-stream-utils.ts +14 -0
- package/src/result-cache.test.ts +195 -0
- package/src/result-cache.ts +181 -0
- package/src/routes/artifact-routes.ts +169 -0
- package/src/routes/log-routes.ts +391 -0
- package/src/routes/status-routes.ts +92 -0
- package/src/routes/webhook-routes.ts +97 -0
- package/src/run-artifact-metadata-cache.test.ts +80 -0
- package/src/run-artifact-metadata-cache.ts +184 -0
- package/src/secret-value-cache.test.ts +66 -0
- package/src/secret-value-cache.ts +55 -0
- package/src/secrets/SecretsManager.ts +343 -0
- package/src/test-utils.ts +81 -0
- package/src/timestamp-tracker.test.ts +134 -0
- package/src/timestamp-tracker.ts +132 -0
- package/src/utils/failure-artifact-writer.ts +187 -0
- package/src/utils/file-helpers.test.ts +235 -0
- package/src/utils/file-helpers.ts +150 -0
- package/src/utils/http-client-factory.test.ts +245 -0
- package/src/utils/http-client-factory.ts +157 -0
- package/src/utils/progress-normalizer.test.ts +442 -0
- package/src/utils/progress-normalizer.ts +68 -0
- package/src/utils/response-helpers.test.ts +122 -0
- package/src/utils/response-helpers.ts +101 -0
- package/src/utils/route-helpers.ts +30 -0
- package/src/utils/status-response-builder.ts +159 -0
- package/src/utils/type-guards.ts +52 -0
- package/src/utils/utf8-helpers.ts +102 -0
- package/src/utils/webhook-event-builder.test.ts +143 -0
- package/src/utils/webhook-event-builder.ts +87 -0
- package/src/webhook-manager.test.ts +152 -0
- package/src/webhook-manager.ts +445 -0
- package/templates/allowlist-api-route.txt +7 -0
- package/templates/allowlist-comprehensive.txt +8 -0
- package/templates/allowlist-parser-fix.txt +6 -0
- package/templates/allowlist-ui-component.txt +9 -0
- package/templates/allowlist-utility.txt +9 -0
- package/test/actual-model-metadata.test.sh +102 -0
- package/test/dry-run.test.sh +131 -0
- package/test/fixtures/kaseki-report-exit-codes/metadata-exit-0.json +1 -0
- package/test/fixtures/kaseki-report-exit-codes/metadata-exit-1.json +1 -0
- package/test/fixtures/kaseki-report-exit-codes/metadata-exit-invalid.json +1 -0
- package/test/fixtures/kaseki-report-exit-codes/metadata-exit-str-0.json +1 -0
- package/test/fixtures/kaseki-report-exit-codes/metadata-exit-str-1.json +1 -0
- package/test/kaseki-api.integration.test.sh +165 -0
- package/test/pi-event-filter-failure.test.sh +83 -0
- package/test/printf-safety-focused.test.sh +99 -0
- package/test/printf-safety-results/results/restoration.jsonl +10 -0
- package/test/printf-safety-results/results/test.jsonl +0 -0
- package/test/printf-safety.test.sh +297 -0
- package/test/validation-fix.test.sh +79 -0
- package/test/validation-integration.test.sh +109 -0
- package/tests/allowlist-glob.test.sh +61 -0
- package/tests/dependency-cache-key.test.sh +48 -0
- package/tests/dependency-restore-mode.test.sh +48 -0
- package/tests/doctor-template-parity.test.sh +95 -0
- package/tests/github-operations.test.sh +142 -0
- package/tests/npm-install-flags.test.sh +58 -0
- package/tests/quality-gates.test.sh +178 -0
- package/tests/repo-memory.test.sh +103 -0
- package/tests/restore-disallowed-changes.test.sh +80 -0
- package/tests/validation-missing-npm-scripts.test.sh +93 -0
- package/tests/validation-strict-mode.test.sh +118 -0
- package/tsconfig.changed.json +7 -0
- package/tsconfig.json +39 -0
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
|
|
4
|
+
export interface RunArtifactFileSnapshot {
|
|
5
|
+
exists: boolean;
|
|
6
|
+
size: number;
|
|
7
|
+
mtimeMs?: number;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export type RunArtifactMetadata = Record<string, RunArtifactFileSnapshot>;
|
|
11
|
+
|
|
12
|
+
interface CacheEntry {
|
|
13
|
+
timestamp: number;
|
|
14
|
+
files: Map<string, RunArtifactFileSnapshot>;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface RunArtifactMetadataCacheOptions {
|
|
18
|
+
ttlMs?: number;
|
|
19
|
+
maxEntries?: number;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const DEFAULT_TTL_MS = 5 * 60 * 1000;
|
|
23
|
+
const DEFAULT_MAX_ENTRIES = 250;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Shared cache for terminal run artifact metadata.
|
|
27
|
+
*
|
|
28
|
+
* Entries are keyed by job id and result directory. Cached file snapshots are
|
|
29
|
+
* validated against current size/mtime before reuse, so terminal status and
|
|
30
|
+
* artifact routes can share availability metadata without diverging when an
|
|
31
|
+
* artifact is rewritten after finalization.
|
|
32
|
+
*/
|
|
33
|
+
export class RunArtifactMetadataCache {
|
|
34
|
+
private readonly ttlMs: number;
|
|
35
|
+
private readonly maxEntries: number;
|
|
36
|
+
private readonly cache = new Map<string, CacheEntry>();
|
|
37
|
+
|
|
38
|
+
constructor(options: RunArtifactMetadataCacheOptions = {}) {
|
|
39
|
+
this.ttlMs = options.ttlMs ?? DEFAULT_TTL_MS;
|
|
40
|
+
this.maxEntries = options.maxEntries ?? DEFAULT_MAX_ENTRIES;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
get(jobId: string, resultDir: string, fileNames: readonly string[], cacheable: boolean): RunArtifactMetadata {
|
|
44
|
+
if (!cacheable) {
|
|
45
|
+
return this.readMetadata(resultDir, fileNames);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const key = this.cacheKey(jobId, resultDir);
|
|
49
|
+
const cached = this.cache.get(key);
|
|
50
|
+
if (cached && this.isUsable(cached, resultDir, fileNames)) {
|
|
51
|
+
cached.timestamp = Date.now();
|
|
52
|
+
return this.project(cached.files, fileNames);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const metadata = this.readMetadata(resultDir, fileNames);
|
|
56
|
+
this.set(key, metadata);
|
|
57
|
+
return metadata;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
clear(jobId?: string, resultDir?: string): void {
|
|
61
|
+
if (!jobId && !resultDir) {
|
|
62
|
+
this.cache.clear();
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const normalizedResultDir = resultDir ? this.normalizeResultDir(resultDir) : undefined;
|
|
67
|
+
for (const key of Array.from(this.cache.keys())) {
|
|
68
|
+
const [entryJobId, entryResultDir] = this.parseKey(key);
|
|
69
|
+
if (jobId && entryJobId !== jobId) {
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
if (normalizedResultDir && entryResultDir !== normalizedResultDir) {
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
this.cache.delete(key);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
getStats(): { entries: number } {
|
|
80
|
+
return { entries: this.cache.size };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
private isUsable(entry: CacheEntry, resultDir: string, fileNames: readonly string[]): boolean {
|
|
84
|
+
if (Date.now() - entry.timestamp >= this.ttlMs) {
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
for (const fileName of fileNames) {
|
|
89
|
+
const cached = entry.files.get(fileName);
|
|
90
|
+
if (!cached) {
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
const current = this.readOne(resultDir, fileName);
|
|
94
|
+
if (!this.sameSnapshot(cached, current)) {
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return true;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
private readMetadata(resultDir: string, fileNames: readonly string[]): RunArtifactMetadata {
|
|
103
|
+
const metadata: RunArtifactMetadata = {};
|
|
104
|
+
for (const fileName of fileNames) {
|
|
105
|
+
metadata[fileName] = this.readOne(resultDir, fileName);
|
|
106
|
+
}
|
|
107
|
+
return metadata;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
private readOne(resultDir: string, fileName: string): RunArtifactFileSnapshot {
|
|
111
|
+
try {
|
|
112
|
+
const stat = fs.statSync(path.join(resultDir, fileName));
|
|
113
|
+
if (!stat.isFile()) {
|
|
114
|
+
return { exists: false, size: 0 };
|
|
115
|
+
}
|
|
116
|
+
return { exists: true, size: stat.size, mtimeMs: stat.mtimeMs };
|
|
117
|
+
} catch {
|
|
118
|
+
return { exists: false, size: 0 };
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
private sameSnapshot(a: RunArtifactFileSnapshot, b: RunArtifactFileSnapshot): boolean {
|
|
123
|
+
if (a.exists !== b.exists || a.size !== b.size) {
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
if (!a.exists && !b.exists) {
|
|
127
|
+
return true;
|
|
128
|
+
}
|
|
129
|
+
return a.mtimeMs === b.mtimeMs;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
private project(files: Map<string, RunArtifactFileSnapshot>, fileNames: readonly string[]): RunArtifactMetadata {
|
|
133
|
+
const metadata: RunArtifactMetadata = {};
|
|
134
|
+
for (const fileName of fileNames) {
|
|
135
|
+
metadata[fileName] = files.get(fileName) ?? { exists: false, size: 0 };
|
|
136
|
+
}
|
|
137
|
+
return metadata;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
private set(key: string, metadata: RunArtifactMetadata): void {
|
|
141
|
+
if (!this.cache.has(key) && this.cache.size >= this.maxEntries) {
|
|
142
|
+
const oldest = Array.from(this.cache.entries()).sort((a, b) => a[1].timestamp - b[1].timestamp)[0];
|
|
143
|
+
if (oldest) {
|
|
144
|
+
this.cache.delete(oldest[0]);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
this.cache.set(key, {
|
|
149
|
+
timestamp: Date.now(),
|
|
150
|
+
files: new Map(Object.entries(metadata)),
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
private cacheKey(jobId: string, resultDir: string): string {
|
|
155
|
+
return `${jobId}\0${this.normalizeResultDir(resultDir)}`;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
private parseKey(key: string): [string, string] {
|
|
159
|
+
const separatorIndex = key.indexOf('\0');
|
|
160
|
+
if (separatorIndex === -1) {
|
|
161
|
+
return [key, ''];
|
|
162
|
+
}
|
|
163
|
+
return [key.slice(0, separatorIndex), key.slice(separatorIndex + 1)];
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
private normalizeResultDir(resultDir: string): string {
|
|
167
|
+
return path.resolve(resultDir);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export const runArtifactMetadataCache = new RunArtifactMetadataCache();
|
|
172
|
+
|
|
173
|
+
export function getRunArtifactMetadata(
|
|
174
|
+
jobId: string,
|
|
175
|
+
resultDir: string,
|
|
176
|
+
fileNames: readonly string[],
|
|
177
|
+
cacheable: boolean
|
|
178
|
+
): RunArtifactMetadata {
|
|
179
|
+
return runArtifactMetadataCache.get(jobId, resultDir, fileNames, cacheable);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export function clearRunArtifactMetadataCache(jobId?: string, resultDir?: string): void {
|
|
183
|
+
runArtifactMetadataCache.clear(jobId, resultDir);
|
|
184
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import { SecretValueCache } from './secret-value-cache';
|
|
4
|
+
|
|
5
|
+
describe('SecretValueCache', () => {
|
|
6
|
+
let testDir: string;
|
|
7
|
+
let cache: SecretValueCache;
|
|
8
|
+
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
testDir = fs.mkdtempSync(path.join('/tmp', 'secret-value-cache-test-'));
|
|
11
|
+
cache = new SecretValueCache();
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
afterEach(() => {
|
|
15
|
+
fs.rmSync(testDir, { recursive: true, force: true });
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test('returns cached file values while mtime and size are unchanged', () => {
|
|
19
|
+
const secretFile = path.join(testDir, 'secret');
|
|
20
|
+
const fixedTime = new Date('2020-01-01T00:00:00.000Z');
|
|
21
|
+
fs.writeFileSync(secretFile, 'secret-one\n');
|
|
22
|
+
fs.utimesSync(secretFile, fixedTime, fixedTime);
|
|
23
|
+
|
|
24
|
+
expect(cache.readSecretValue(undefined, secretFile)).toBe('secret-one');
|
|
25
|
+
|
|
26
|
+
fs.writeFileSync(secretFile, 'secret-two\n');
|
|
27
|
+
fs.utimesSync(secretFile, fixedTime, fixedTime);
|
|
28
|
+
|
|
29
|
+
expect(cache.readSecretValue(undefined, secretFile)).toBe('secret-one');
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test('rereads file values when file metadata changes', () => {
|
|
33
|
+
const secretFile = path.join(testDir, 'secret');
|
|
34
|
+
fs.writeFileSync(secretFile, 'old-secret\n');
|
|
35
|
+
|
|
36
|
+
expect(cache.readSecretValue(undefined, secretFile)).toBe('old-secret');
|
|
37
|
+
|
|
38
|
+
fs.writeFileSync(secretFile, 'new-secret-value\n');
|
|
39
|
+
|
|
40
|
+
expect(cache.readSecretValue(undefined, secretFile)).toBe('new-secret-value');
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test('prefers inline values over file values', () => {
|
|
44
|
+
const secretFile = path.join(testDir, 'secret');
|
|
45
|
+
fs.writeFileSync(secretFile, 'file-secret\n');
|
|
46
|
+
fs.rmSync(secretFile);
|
|
47
|
+
|
|
48
|
+
expect(cache.readSecretValue(' inline-secret ', secretFile)).toBe('inline-secret');
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test('clear drops cached values for tests', () => {
|
|
52
|
+
const secretFile = path.join(testDir, 'secret');
|
|
53
|
+
const fixedTime = new Date('2020-01-01T00:00:00.000Z');
|
|
54
|
+
fs.writeFileSync(secretFile, 'old-secret\n');
|
|
55
|
+
fs.utimesSync(secretFile, fixedTime, fixedTime);
|
|
56
|
+
|
|
57
|
+
expect(cache.readSecretValue(undefined, secretFile)).toBe('old-secret');
|
|
58
|
+
|
|
59
|
+
fs.writeFileSync(secretFile, 'new-secret\n');
|
|
60
|
+
fs.utimesSync(secretFile, fixedTime, fixedTime);
|
|
61
|
+
expect(cache.readSecretValue(undefined, secretFile)).toBe('old-secret');
|
|
62
|
+
|
|
63
|
+
cache.clear();
|
|
64
|
+
expect(cache.readSecretValue(undefined, secretFile)).toBe('new-secret');
|
|
65
|
+
});
|
|
66
|
+
});
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
|
|
3
|
+
type SecretCacheEntry = {
|
|
4
|
+
value: string;
|
|
5
|
+
mtimeMs: number;
|
|
6
|
+
size: number;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Small synchronous cache for secret files that are read repeatedly while the
|
|
11
|
+
* process stays alive. Entries are keyed by file path and invalidated whenever
|
|
12
|
+
* the file metadata changes.
|
|
13
|
+
*/
|
|
14
|
+
export class SecretValueCache {
|
|
15
|
+
private entries = new Map<string, SecretCacheEntry>();
|
|
16
|
+
|
|
17
|
+
readFile(filePath: string): string {
|
|
18
|
+
const stat = fs.statSync(filePath);
|
|
19
|
+
const cached = this.entries.get(filePath);
|
|
20
|
+
if (cached && cached.mtimeMs === stat.mtimeMs && cached.size === stat.size) {
|
|
21
|
+
return cached.value;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const value = fs.readFileSync(filePath, 'utf-8');
|
|
25
|
+
this.entries.set(filePath, {
|
|
26
|
+
value,
|
|
27
|
+
mtimeMs: stat.mtimeMs,
|
|
28
|
+
size: stat.size,
|
|
29
|
+
});
|
|
30
|
+
return value;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
readSecretValue(inlineValue?: string, filePath?: string): string | undefined {
|
|
34
|
+
const trimmedInline = inlineValue?.trim();
|
|
35
|
+
if (trimmedInline) {
|
|
36
|
+
return trimmedInline;
|
|
37
|
+
}
|
|
38
|
+
if (!filePath) {
|
|
39
|
+
return undefined;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
try {
|
|
43
|
+
const value = this.readFile(filePath).replace(/^\uFEFF/, '').trim();
|
|
44
|
+
return value || undefined;
|
|
45
|
+
} catch {
|
|
46
|
+
return undefined;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
clear(): void {
|
|
51
|
+
this.entries.clear();
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export const secretValueCache = new SecretValueCache();
|
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Secrets Manager
|
|
3
|
+
*
|
|
4
|
+
* Secure credential storage with keyring integration
|
|
5
|
+
* - Primary: Linux keyring (pass)
|
|
6
|
+
* - Fallback: File-based storage (~/.kaseki/secrets/) with 0600 permissions
|
|
7
|
+
*
|
|
8
|
+
* Note: macOS Keychain and Windows Credential Manager support can be added
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import fs from 'fs/promises';
|
|
12
|
+
import path from 'path';
|
|
13
|
+
import os from 'os';
|
|
14
|
+
import { execSync } from 'child_process';
|
|
15
|
+
import { createLogger } from '../logger';
|
|
16
|
+
|
|
17
|
+
const logger = createLogger('secrets');
|
|
18
|
+
|
|
19
|
+
export interface SecretsStore {
|
|
20
|
+
store(key: string, value: string): Promise<void>;
|
|
21
|
+
retrieve(key: string): Promise<string | null>;
|
|
22
|
+
delete(key: string): Promise<void>;
|
|
23
|
+
list(): Promise<string[]>;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* File-based secrets store (fallback, headless systems)
|
|
28
|
+
*/
|
|
29
|
+
export class FileSecretsStore implements SecretsStore {
|
|
30
|
+
private baseDir: string;
|
|
31
|
+
|
|
32
|
+
constructor(baseDir?: string) {
|
|
33
|
+
this.baseDir = baseDir || path.join(os.homedir(), '.kaseki', 'secrets');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async store(key: string, value: string): Promise<void> {
|
|
37
|
+
try {
|
|
38
|
+
await fs.mkdir(this.baseDir, { recursive: true, mode: 0o700 });
|
|
39
|
+
const filePath = path.join(this.baseDir, key);
|
|
40
|
+
|
|
41
|
+
// Write with restrictive permissions (0600 - owner read/write only)
|
|
42
|
+
await fs.writeFile(filePath, value, {
|
|
43
|
+
mode: 0o600,
|
|
44
|
+
flag: 'w',
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
logger.debug(`Secret stored: ${key} -> ${filePath}`);
|
|
48
|
+
} catch (error) {
|
|
49
|
+
throw new Error(`Failed to store secret: ${error}`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async retrieve(key: string): Promise<string | null> {
|
|
54
|
+
try {
|
|
55
|
+
const filePath = path.join(this.baseDir, key);
|
|
56
|
+
const stat = await fs.stat(filePath);
|
|
57
|
+
|
|
58
|
+
// Security check: verify file permissions are restrictive
|
|
59
|
+
if ((stat.mode & 0o077) !== 0) {
|
|
60
|
+
logger.warn(`Secret file has overly permissive permissions: ${filePath}`);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const value = await fs.readFile(filePath, 'utf-8');
|
|
64
|
+
return value.trim();
|
|
65
|
+
} catch (error) {
|
|
66
|
+
if ((error as NodeJS.ErrnoException).code === 'ENOENT') {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
throw new Error(`Failed to retrieve secret: ${error}`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async delete(key: string): Promise<void> {
|
|
74
|
+
try {
|
|
75
|
+
const filePath = path.join(this.baseDir, key);
|
|
76
|
+
await fs.unlink(filePath);
|
|
77
|
+
logger.debug(`Secret deleted: ${key}`);
|
|
78
|
+
} catch (error) {
|
|
79
|
+
if ((error as NodeJS.ErrnoException).code !== 'ENOENT') {
|
|
80
|
+
throw new Error(`Failed to delete secret: ${error}`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async list(): Promise<string[]> {
|
|
86
|
+
try {
|
|
87
|
+
const files = await fs.readdir(this.baseDir);
|
|
88
|
+
return files;
|
|
89
|
+
} catch (error) {
|
|
90
|
+
if ((error as NodeJS.ErrnoException).code === 'ENOENT') {
|
|
91
|
+
return [];
|
|
92
|
+
}
|
|
93
|
+
throw error;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Linux pass (password-store) backed secrets
|
|
100
|
+
* Requires: pass package installed and initialized
|
|
101
|
+
*/
|
|
102
|
+
export class PassSecretsStore implements SecretsStore {
|
|
103
|
+
private prefix: string;
|
|
104
|
+
|
|
105
|
+
constructor(prefix: string = 'kaseki-agent') {
|
|
106
|
+
this.prefix = prefix;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
private getPassKey(key: string): string {
|
|
110
|
+
return `${this.prefix}/${key}`;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
private isPassAvailable(): boolean {
|
|
114
|
+
try {
|
|
115
|
+
execSync('command -v pass', { shell: '/bin/bash', stdio: 'ignore' });
|
|
116
|
+
return true;
|
|
117
|
+
} catch {
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
private isPassInitialized(): boolean {
|
|
123
|
+
try {
|
|
124
|
+
execSync('pass ls > /dev/null 2>&1', { shell: '/bin/bash', stdio: 'ignore' });
|
|
125
|
+
return true;
|
|
126
|
+
} catch {
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
async store(key: string, value: string): Promise<void> {
|
|
132
|
+
if (!this.isPassAvailable()) {
|
|
133
|
+
throw new Error('pass (password-store) not installed. Install with: sudo apt install pass');
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (!this.isPassInitialized()) {
|
|
137
|
+
throw new Error('pass (password-store) not initialized. Run: pass init');
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
try {
|
|
141
|
+
// Use echo + pass insert to avoid interactive prompt
|
|
142
|
+
const passKey = this.getPassKey(key);
|
|
143
|
+
execSync(`echo "${value.replace(/"/g, '\\"')}" | pass insert -f "${passKey}"`, {
|
|
144
|
+
shell: '/bin/bash',
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
logger.debug(`Secret stored in pass: ${passKey}`);
|
|
148
|
+
} catch (error) {
|
|
149
|
+
throw new Error(`Failed to store secret in pass: ${error}`);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
async retrieve(key: string): Promise<string | null> {
|
|
154
|
+
if (!this.isPassAvailable()) {
|
|
155
|
+
logger.debug('pass not available, skipping keyring retrieval');
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
try {
|
|
160
|
+
const passKey = this.getPassKey(key);
|
|
161
|
+
const result = execSync(`pass show "${passKey}" 2>/dev/null || echo ""`, {
|
|
162
|
+
shell: '/bin/bash',
|
|
163
|
+
encoding: 'utf-8',
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
return result.trim() || null;
|
|
167
|
+
} catch (error) {
|
|
168
|
+
logger.debug(`Failed to retrieve secret from pass: ${error}`);
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
async delete(key: string): Promise<void> {
|
|
174
|
+
if (!this.isPassAvailable()) {
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
try {
|
|
179
|
+
const passKey = this.getPassKey(key);
|
|
180
|
+
execSync(`pass rm -f "${passKey}"`, { shell: '/bin/bash' });
|
|
181
|
+
logger.debug(`Secret deleted from pass: ${passKey}`);
|
|
182
|
+
} catch (error) {
|
|
183
|
+
throw new Error(`Failed to delete secret from pass: ${error}`);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
async list(): Promise<string[]> {
|
|
188
|
+
if (!this.isPassAvailable()) {
|
|
189
|
+
return [];
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
try {
|
|
193
|
+
const result = execSync(`pass ls 2>/dev/null | grep "^${this.prefix}/" || echo ""`, {
|
|
194
|
+
shell: '/bin/bash',
|
|
195
|
+
encoding: 'utf-8',
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
return result
|
|
199
|
+
.split('\n')
|
|
200
|
+
.filter((line) => line.trim())
|
|
201
|
+
.map((line) => line.replace(`${this.prefix}/`, ''));
|
|
202
|
+
} catch (error) {
|
|
203
|
+
logger.debug(`Failed to list secrets from pass: ${error}`);
|
|
204
|
+
return [];
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Secrets Manager - Main interface
|
|
211
|
+
* Tries keyring first, falls back to file storage
|
|
212
|
+
*/
|
|
213
|
+
export class SecretsManager {
|
|
214
|
+
private fileStore: FileSecretsStore;
|
|
215
|
+
private passStore: PassSecretsStore;
|
|
216
|
+
private usePassIfAvailable: boolean;
|
|
217
|
+
|
|
218
|
+
constructor(usePassIfAvailable: boolean = true) {
|
|
219
|
+
this.fileStore = new FileSecretsStore();
|
|
220
|
+
this.passStore = new PassSecretsStore();
|
|
221
|
+
this.usePassIfAvailable = usePassIfAvailable;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Store a secret with automatic fallback
|
|
226
|
+
*/
|
|
227
|
+
async store(key: string, value: string): Promise<void> {
|
|
228
|
+
if (this.usePassIfAvailable && this.isPassAvailable()) {
|
|
229
|
+
try {
|
|
230
|
+
await this.passStore.store(key, value);
|
|
231
|
+
return;
|
|
232
|
+
} catch (error) {
|
|
233
|
+
logger.warn(`Keyring storage failed, falling back to file storage: ${error}`);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
await this.fileStore.store(key, value);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Retrieve a secret (tries keyring first, then file)
|
|
242
|
+
*/
|
|
243
|
+
async retrieve(key: string): Promise<string | null> {
|
|
244
|
+
// Try file-based first (where users might have stored it)
|
|
245
|
+
const fileValue = await this.fileStore.retrieve(key);
|
|
246
|
+
if (fileValue) {
|
|
247
|
+
return fileValue;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Try keyring if available
|
|
251
|
+
if (this.usePassIfAvailable && this.isPassAvailable()) {
|
|
252
|
+
const passValue = await this.passStore.retrieve(key);
|
|
253
|
+
if (passValue) {
|
|
254
|
+
return passValue;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
return null;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Delete a secret from both stores
|
|
263
|
+
*/
|
|
264
|
+
async delete(key: string): Promise<void> {
|
|
265
|
+
try {
|
|
266
|
+
await this.fileStore.delete(key);
|
|
267
|
+
} catch (error) {
|
|
268
|
+
logger.debug(`Failed to delete from file store: ${error}`);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
if (this.usePassIfAvailable && this.isPassAvailable()) {
|
|
272
|
+
try {
|
|
273
|
+
await this.passStore.delete(key);
|
|
274
|
+
} catch (error) {
|
|
275
|
+
logger.debug(`Failed to delete from keyring: ${error}`);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* List all stored secrets
|
|
282
|
+
*/
|
|
283
|
+
async list(): Promise<Map<string, string>> {
|
|
284
|
+
const secrets = new Map<string, string>();
|
|
285
|
+
|
|
286
|
+
// List from file store
|
|
287
|
+
const fileKeys = await this.fileStore.list();
|
|
288
|
+
for (const key of fileKeys) {
|
|
289
|
+
const value = await this.fileStore.retrieve(key);
|
|
290
|
+
if (value) {
|
|
291
|
+
secrets.set(key, `file:${path.join(os.homedir(), '.kaseki', 'secrets', key)}`);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// List from keyring
|
|
296
|
+
if (this.usePassIfAvailable && this.isPassAvailable()) {
|
|
297
|
+
const passKeys = await this.passStore.list();
|
|
298
|
+
for (const key of passKeys) {
|
|
299
|
+
secrets.set(key, `keyring:${key}`);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
return secrets;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Check if pass is available
|
|
308
|
+
*/
|
|
309
|
+
private isPassAvailable(): boolean {
|
|
310
|
+
try {
|
|
311
|
+
execSync('command -v pass', { shell: '/bin/bash', stdio: 'ignore' });
|
|
312
|
+
return true;
|
|
313
|
+
} catch {
|
|
314
|
+
return false;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Initialize/setup keyring (interactive)
|
|
320
|
+
*/
|
|
321
|
+
async initializeKeyring(): Promise<void> {
|
|
322
|
+
if (!this.isPassAvailable()) {
|
|
323
|
+
throw new Error('pass (password-store) not installed');
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
try {
|
|
327
|
+
execSync('pass init 2>&1', { shell: '/bin/bash', stdio: 'inherit' });
|
|
328
|
+
logger.info('Keyring initialized successfully');
|
|
329
|
+
} catch (error) {
|
|
330
|
+
throw new Error(`Failed to initialize keyring: ${error}`);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Get recommended storage method for system
|
|
336
|
+
*/
|
|
337
|
+
getRecommendedStore(): string {
|
|
338
|
+
if (this.isPassAvailable()) {
|
|
339
|
+
return 'keyring (pass)';
|
|
340
|
+
}
|
|
341
|
+
return 'file (~/.kaseki/secrets/)';
|
|
342
|
+
}
|
|
343
|
+
}
|