@ouro.bot/cli 0.1.0-alpha.40 → 0.1.0-alpha.401
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/README.md +109 -14
- package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/agent.json +3 -2
- package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/SOUL.md +2 -2
- package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/the-serpent.md +1 -1
- package/changelog.json +2465 -6
- package/dist/arc/attention-types.js +8 -0
- package/dist/arc/cares.js +140 -0
- package/dist/arc/episodes.js +117 -0
- package/dist/arc/intentions.js +133 -0
- package/dist/arc/json-store.js +117 -0
- package/dist/arc/obligations.js +237 -0
- package/dist/arc/packets.js +193 -0
- package/dist/arc/presence.js +185 -0
- package/dist/arc/task-lifecycle.js +65 -0
- package/dist/heart/active-work.js +832 -0
- package/dist/heart/agent-entry.js +58 -3
- package/dist/heart/attachments/image-normalize.js +194 -0
- package/dist/heart/attachments/materialize.js +97 -0
- package/dist/heart/attachments/originals.js +88 -0
- package/dist/heart/attachments/render.js +29 -0
- package/dist/heart/attachments/sources/adapter.js +2 -0
- package/dist/heart/attachments/sources/bluebubbles.js +156 -0
- package/dist/heart/attachments/sources/cli-local-file.js +78 -0
- package/dist/heart/attachments/sources/index.js +16 -0
- package/dist/heart/attachments/store.js +103 -0
- package/dist/heart/attachments/types.js +93 -0
- package/dist/heart/auth/auth-flow.js +417 -0
- package/dist/heart/bridges/manager.js +358 -0
- package/dist/heart/bridges/state-machine.js +135 -0
- package/dist/heart/bridges/store.js +123 -0
- package/dist/heart/bundle-state.js +168 -0
- package/dist/heart/commitments.js +111 -0
- package/dist/heart/config-registry.js +304 -0
- package/dist/heart/config.js +101 -128
- package/dist/heart/core.js +801 -217
- package/dist/heart/cross-chat-delivery.js +131 -0
- package/dist/heart/daemon/agent-config-check.js +397 -0
- package/dist/heart/daemon/agent-discovery.js +79 -3
- package/dist/heart/daemon/agent-service.js +360 -0
- package/dist/heart/daemon/agentic-repair.js +213 -0
- package/dist/heart/daemon/bluebubbles-health-diagnostics.js +122 -0
- package/dist/heart/daemon/cadence.js +70 -0
- package/dist/heart/daemon/cli-defaults.js +599 -0
- package/dist/heart/daemon/cli-exec.js +3616 -0
- package/dist/heart/daemon/cli-help.js +396 -0
- package/dist/heart/daemon/cli-parse.js +1118 -0
- package/dist/heart/daemon/cli-render-doctor.js +57 -0
- package/dist/heart/daemon/cli-render.js +560 -0
- package/dist/heart/daemon/cli-types.js +8 -0
- package/dist/heart/daemon/daemon-cli.js +28 -1582
- package/dist/heart/daemon/daemon-entry.js +356 -3
- package/dist/heart/daemon/daemon-health.js +141 -0
- package/dist/heart/daemon/daemon-runtime-sync.js +157 -12
- package/dist/heart/daemon/daemon-tombstone.js +236 -0
- package/dist/heart/daemon/daemon.js +684 -58
- package/dist/heart/daemon/doctor-types.js +8 -0
- package/dist/heart/daemon/doctor.js +419 -0
- package/dist/heart/daemon/health-monitor.js +79 -1
- package/dist/heart/daemon/hooks/agent-config-v2.js +33 -0
- package/dist/heart/daemon/hooks/bundle-meta.js +115 -1
- package/dist/heart/daemon/http-health-probe.js +80 -0
- package/dist/heart/daemon/inner-status.js +89 -0
- package/dist/heart/daemon/interactive-repair.js +209 -0
- package/dist/heart/daemon/launchd.js +46 -9
- package/dist/heart/daemon/log-tailer.js +82 -12
- package/dist/heart/daemon/logs-prune.js +105 -0
- package/dist/heart/daemon/message-router.js +17 -8
- package/dist/heart/daemon/os-cron-deps.js +134 -0
- package/dist/heart/daemon/ouro-bot-entry.js +4 -2
- package/dist/heart/daemon/ouro-entry.js +3 -1
- package/dist/heart/daemon/process-manager.js +201 -0
- package/dist/heart/daemon/provider-discovery.js +137 -0
- package/dist/heart/daemon/pulse.js +475 -0
- package/dist/heart/daemon/readiness-repair.js +216 -0
- package/dist/heart/daemon/run-hooks.js +2 -0
- package/dist/heart/daemon/runtime-logging.js +67 -16
- package/dist/heart/daemon/runtime-metadata.js +73 -0
- package/dist/heart/daemon/runtime-mode.js +67 -0
- package/dist/heart/daemon/safe-mode.js +161 -0
- package/dist/heart/daemon/sense-manager.js +119 -30
- package/dist/heart/daemon/session-id-resolver.js +131 -0
- package/dist/heart/daemon/skill-management-installer.js +94 -0
- package/dist/heart/daemon/socket-client.js +307 -0
- package/dist/heart/daemon/stale-bundle-prune.js +96 -0
- package/dist/heart/daemon/startup-tui.js +237 -0
- package/dist/heart/daemon/task-scheduler.js +3 -25
- package/dist/heart/daemon/thoughts.js +510 -0
- package/dist/heart/daemon/up-progress.js +135 -0
- package/dist/heart/delegation.js +62 -0
- package/dist/heart/habits/habit-migration.js +181 -0
- package/dist/heart/habits/habit-parser.js +140 -0
- package/dist/heart/habits/habit-scheduler.js +371 -0
- package/dist/heart/{daemon → hatch}/hatch-flow.js +53 -117
- package/dist/heart/{daemon → hatch}/hatch-specialist.js +3 -3
- package/dist/heart/{daemon → hatch}/specialist-prompt.js +12 -9
- package/dist/heart/{daemon → hatch}/specialist-tools.js +33 -12
- package/dist/heart/identity.js +161 -65
- package/dist/heart/kept-notes.js +357 -0
- package/dist/heart/kicks.js +1 -1
- package/dist/heart/machine-identity.js +161 -0
- package/dist/heart/mcp/mcp-server.js +653 -0
- package/dist/heart/migrate-config.js +100 -0
- package/dist/heart/model-capabilities.js +59 -0
- package/dist/heart/outlook/outlook-http-hooks.js +64 -0
- package/dist/heart/outlook/outlook-http-response.js +7 -0
- package/dist/heart/outlook/outlook-http-routes.js +232 -0
- package/dist/heart/outlook/outlook-http-static.js +99 -0
- package/dist/heart/outlook/outlook-http-transport.js +116 -0
- package/dist/heart/outlook/outlook-http.js +99 -0
- package/dist/heart/outlook/outlook-read.js +28 -0
- package/dist/heart/outlook/outlook-types.js +27 -0
- package/dist/heart/outlook/outlook-view.js +195 -0
- package/dist/heart/outlook/readers/agent-machine.js +359 -0
- package/dist/heart/outlook/readers/continuity-readers.js +332 -0
- package/dist/heart/outlook/readers/runtime-readers.js +660 -0
- package/dist/heart/outlook/readers/sessions.js +232 -0
- package/dist/heart/outlook/readers/shared.js +111 -0
- package/dist/heart/platform.js +81 -0
- package/dist/heart/progress-story.js +42 -0
- package/dist/heart/provider-attempt.js +133 -0
- package/dist/heart/provider-binding-resolver.js +239 -0
- package/dist/heart/provider-credentials.js +383 -0
- package/dist/heart/provider-failover.js +266 -0
- package/dist/heart/provider-models.js +81 -0
- package/dist/heart/provider-ping.js +237 -0
- package/dist/heart/provider-state.js +216 -0
- package/dist/heart/provider-visibility.js +186 -0
- package/dist/heart/providers/anthropic-token.js +131 -0
- package/dist/heart/providers/anthropic.js +193 -55
- package/dist/heart/providers/azure.js +103 -12
- package/dist/heart/providers/error-classification.js +63 -0
- package/dist/heart/providers/github-copilot.js +145 -0
- package/dist/heart/providers/minimax-vlm.js +189 -0
- package/dist/heart/providers/minimax.js +29 -7
- package/dist/heart/providers/openai-codex.js +39 -29
- package/dist/heart/runtime-credentials.js +181 -0
- package/dist/heart/session-activity.js +190 -0
- package/dist/heart/session-events.js +855 -0
- package/dist/heart/session-transcript.js +167 -0
- package/dist/heart/start-of-turn-packet.js +345 -0
- package/dist/heart/streaming.js +36 -27
- package/dist/heart/sync.js +332 -0
- package/dist/heart/target-resolution.js +127 -0
- package/dist/heart/tempo.js +93 -0
- package/dist/heart/temporal-view.js +41 -0
- package/dist/heart/tool-activity-callbacks.js +36 -0
- package/dist/heart/tool-description.js +135 -0
- package/dist/heart/tool-friction.js +55 -0
- package/dist/heart/tool-loop.js +200 -0
- package/dist/heart/turn-context.js +351 -0
- package/dist/heart/turn-coordinator.js +28 -0
- package/dist/heart/{daemon → versioning}/ouro-bot-global-installer.js +1 -1
- package/dist/heart/{daemon → versioning}/ouro-bot-wrapper.js +1 -1
- package/dist/heart/versioning/ouro-path-installer.js +301 -0
- package/dist/heart/versioning/ouro-version-manager.js +295 -0
- package/dist/heart/{daemon → versioning}/staged-restart.js +40 -8
- package/dist/heart/{daemon → versioning}/update-checker.js +12 -2
- package/dist/heart/{daemon → versioning}/update-hooks.js +63 -59
- package/dist/mind/bundle-manifest.js +7 -1
- package/dist/mind/context.js +134 -87
- package/dist/mind/diary-integrity.js +60 -0
- package/dist/mind/{memory.js → diary.js} +84 -96
- package/dist/mind/embedding-provider.js +60 -0
- package/dist/mind/file-state.js +179 -0
- package/dist/mind/first-impressions.js +14 -1
- package/dist/mind/friends/channel.js +21 -0
- package/dist/mind/friends/group-context.js +144 -0
- package/dist/mind/friends/resolver.js +38 -1
- package/dist/mind/friends/store-file.js +39 -3
- package/dist/mind/friends/trust-explanation.js +74 -0
- package/dist/mind/friends/types.js +1 -1
- package/dist/mind/journal-index.js +161 -0
- package/dist/mind/note-search.js +268 -0
- package/dist/mind/obligation-steering.js +221 -0
- package/dist/mind/pending.js +66 -7
- package/dist/mind/prompt-refresh.js +3 -2
- package/dist/mind/prompt.js +946 -167
- package/dist/mind/provenance-trust.js +26 -0
- package/dist/mind/scrutiny.js +173 -0
- package/dist/nerves/cli-logging.js +7 -1
- package/dist/nerves/coverage/audit-rules.js +15 -6
- package/dist/nerves/coverage/audit.js +28 -2
- package/dist/nerves/coverage/cli.js +1 -1
- package/dist/nerves/coverage/contract.js +5 -5
- package/dist/nerves/coverage/file-completeness.js +83 -5
- package/dist/nerves/coverage/run-artifacts.js +1 -1
- package/dist/nerves/event-buffer.js +111 -0
- package/dist/nerves/index.js +224 -4
- package/dist/nerves/observation.js +20 -0
- package/dist/nerves/redact.js +79 -0
- package/dist/nerves/runtime.js +5 -1
- package/dist/outlook-ui/assets/index-BAcU08c-.css +1 -0
- package/dist/outlook-ui/assets/index-D7l3l4vY.js +61 -0
- package/dist/outlook-ui/index.html +15 -0
- package/dist/repertoire/ado-client.js +15 -56
- package/dist/repertoire/ado-semantic.js +11 -10
- package/dist/repertoire/api-client.js +97 -0
- package/dist/repertoire/bitwarden-store.js +519 -0
- package/dist/repertoire/bundle-templates.js +72 -0
- package/dist/repertoire/bw-installer.js +79 -0
- package/dist/repertoire/coding/codex-jsonl.js +64 -0
- package/dist/repertoire/coding/context-pack.js +330 -0
- package/dist/repertoire/coding/feedback.js +197 -30
- package/dist/repertoire/coding/manager.js +158 -9
- package/dist/repertoire/coding/spawner.js +55 -9
- package/dist/repertoire/coding/tools.js +170 -7
- package/dist/repertoire/commerce-errors.js +109 -0
- package/dist/repertoire/commerce-self-test.js +156 -0
- package/dist/repertoire/credential-access.js +107 -0
- package/dist/repertoire/duffel-client.js +185 -0
- package/dist/repertoire/github-client.js +14 -55
- package/dist/repertoire/graph-client.js +11 -52
- package/dist/repertoire/guardrails.js +371 -0
- package/dist/repertoire/mcp-client.js +255 -0
- package/dist/repertoire/mcp-manager.js +305 -0
- package/dist/repertoire/mcp-tools.js +63 -0
- package/dist/repertoire/shell-sessions.js +133 -0
- package/dist/repertoire/skills.js +15 -24
- package/dist/repertoire/stripe-client.js +131 -0
- package/dist/repertoire/tasks/board.js +43 -5
- package/dist/repertoire/tasks/fix.js +182 -0
- package/dist/repertoire/tasks/index.js +26 -1
- package/dist/repertoire/tasks/lifecycle.js +2 -2
- package/dist/repertoire/tasks/parser.js +3 -2
- package/dist/repertoire/tasks/scanner.js +194 -37
- package/dist/repertoire/tasks/transitions.js +16 -78
- package/dist/repertoire/tool-results.js +29 -0
- package/dist/repertoire/tools-attachments.js +317 -0
- package/dist/repertoire/tools-base.js +42 -687
- package/dist/repertoire/tools-bluebubbles.js +1 -0
- package/dist/repertoire/tools-bridge.js +141 -0
- package/dist/repertoire/tools-bundle.js +984 -0
- package/dist/repertoire/tools-config.js +185 -0
- package/dist/repertoire/tools-continuity.js +248 -0
- package/dist/repertoire/tools-credential.js +182 -0
- package/dist/repertoire/tools-files.js +342 -0
- package/dist/repertoire/tools-flight.js +224 -0
- package/dist/repertoire/tools-flow.js +105 -0
- package/dist/repertoire/tools-github.js +1 -7
- package/dist/repertoire/tools-notes.js +376 -0
- package/dist/repertoire/tools-session.js +739 -0
- package/dist/repertoire/tools-shell.js +120 -0
- package/dist/repertoire/tools-stripe.js +180 -0
- package/dist/repertoire/tools-surface.js +243 -0
- package/dist/repertoire/tools-teams.js +9 -39
- package/dist/repertoire/tools-travel.js +125 -0
- package/dist/repertoire/tools-user-profile.js +144 -0
- package/dist/repertoire/tools-vault.js +40 -0
- package/dist/repertoire/tools.js +144 -113
- package/dist/repertoire/travel-api-client.js +360 -0
- package/dist/repertoire/user-profile.js +118 -0
- package/dist/repertoire/vault-setup.js +246 -0
- package/dist/repertoire/vault-unlock.js +382 -0
- package/dist/scripts/claude-code-hook.js +41 -0
- package/dist/scripts/claude-code-stop-hook.js +47 -0
- package/dist/senses/attention-queue.js +116 -0
- package/dist/senses/bluebubbles/attachment-cache.js +53 -0
- package/dist/senses/bluebubbles/attachment-download.js +137 -0
- package/dist/senses/{bluebubbles-client.js → bluebubbles/client.js} +260 -9
- package/dist/senses/bluebubbles/entry.js +70 -0
- package/dist/senses/bluebubbles/inbound-log.js +113 -0
- package/dist/senses/bluebubbles/index.js +1620 -0
- package/dist/senses/{bluebubbles-media.js → bluebubbles/media.js} +121 -70
- package/dist/senses/{bluebubbles-model.js → bluebubbles/model.js} +33 -12
- package/dist/senses/{bluebubbles-mutation-log.js → bluebubbles/mutation-log.js} +45 -3
- package/dist/senses/bluebubbles/replay.js +129 -0
- package/dist/senses/bluebubbles/runtime-state.js +109 -0
- package/dist/senses/{bluebubbles-session-cleanup.js → bluebubbles/session-cleanup.js} +1 -1
- package/dist/senses/cli/bracketed-paste.js +82 -0
- package/dist/senses/cli/image-paste.js +287 -0
- package/dist/senses/cli/image-ref-navigation.js +75 -0
- package/dist/senses/cli/ink-app.js +156 -0
- package/dist/senses/cli/inline-diff.js +64 -0
- package/dist/senses/cli/input-keys.js +174 -0
- package/dist/senses/cli/kill-ring.js +86 -0
- package/dist/senses/cli/message-list.js +51 -0
- package/dist/senses/cli/ouro-tui.js +605 -0
- package/dist/senses/cli/spinner-imperative.js +135 -0
- package/dist/senses/cli/spinner.js +101 -0
- package/dist/senses/cli/status-line.js +60 -0
- package/dist/senses/cli/streaming-markdown.js +526 -0
- package/dist/senses/cli/tool-display.js +83 -0
- package/dist/senses/cli/tool-render.js +85 -0
- package/dist/senses/cli/tui-store.js +240 -0
- package/dist/senses/cli/virtual-list.js +35 -0
- package/dist/senses/cli-entry.js +60 -8
- package/dist/senses/cli-layout.js +187 -0
- package/dist/senses/cli.js +526 -211
- package/dist/senses/commands.js +66 -3
- package/dist/senses/continuity.js +94 -0
- package/dist/senses/habit-turn-message.js +108 -0
- package/dist/senses/inner-dialog-worker.js +112 -19
- package/dist/senses/inner-dialog.js +600 -95
- package/dist/senses/pipeline.js +539 -61
- package/dist/senses/proactive-content-guard.js +51 -0
- package/dist/senses/shared-turn.js +205 -0
- package/dist/senses/surface-tool.js +68 -0
- package/dist/senses/teams-entry.js +60 -8
- package/dist/senses/teams.js +569 -237
- package/dist/senses/trust-gate.js +5 -5
- package/package.json +28 -7
- package/skills/agent-commerce.md +106 -0
- package/skills/browser-navigation.md +110 -0
- package/skills/commerce-setup-guide.md +116 -0
- package/skills/commerce-setup.md +84 -0
- package/skills/configure-dev-tools.md +101 -0
- package/skills/travel-planning.md +134 -0
- package/dist/heart/daemon/ouro-path-installer.js +0 -178
- package/dist/heart/daemon/subagent-installer.js +0 -134
- package/dist/mind/associative-recall.js +0 -197
- package/dist/senses/bluebubbles-entry.js +0 -11
- package/dist/senses/bluebubbles.js +0 -832
- package/dist/senses/debug-activity.js +0 -127
- package/subagents/README.md +0 -60
- package/subagents/work-doer.md +0 -235
- package/subagents/work-merger.md +0 -618
- package/subagents/work-planner.md +0 -382
- /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/basilisk.md +0 -0
- /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/jafar.md +0 -0
- /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/jormungandr.md +0 -0
- /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/kaa.md +0 -0
- /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/medusa.md +0 -0
- /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/monty.md +0 -0
- /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/nagini.md +0 -0
- /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/ouroboros.md +0 -0
- /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/python.md +0 -0
- /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/quetzalcoatl.md +0 -0
- /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/sir-hiss.md +0 -0
- /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/the-snake.md +0 -0
- /package/dist/heart/{daemon → hatch}/hatch-animation.js +0 -0
- /package/dist/heart/{daemon → hatch}/specialist-orchestrator.js +0 -0
- /package/dist/heart/{daemon → versioning}/ouro-uti.js +0 -0
- /package/dist/heart/{daemon → versioning}/wrapper-publish-guard.js +0 -0
|
@@ -0,0 +1,519 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Bitwarden CLI credential store — wraps `bw` CLI for the agent's own vault.
|
|
4
|
+
*
|
|
5
|
+
* This store authenticates directly as the agent using its own master password.
|
|
6
|
+
* The agent owns the vault, so no human-in-the-loop is needed.
|
|
7
|
+
*
|
|
8
|
+
* Requires the `bw` CLI to be installed. Session tokens are cached process-local.
|
|
9
|
+
*/
|
|
10
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
11
|
+
if (k2 === undefined) k2 = k;
|
|
12
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
13
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
14
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
15
|
+
}
|
|
16
|
+
Object.defineProperty(o, k2, desc);
|
|
17
|
+
}) : (function(o, m, k, k2) {
|
|
18
|
+
if (k2 === undefined) k2 = k;
|
|
19
|
+
o[k2] = m[k];
|
|
20
|
+
}));
|
|
21
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
22
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
23
|
+
}) : function(o, v) {
|
|
24
|
+
o["default"] = v;
|
|
25
|
+
});
|
|
26
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
27
|
+
var ownKeys = function(o) {
|
|
28
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
29
|
+
var ar = [];
|
|
30
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
31
|
+
return ar;
|
|
32
|
+
};
|
|
33
|
+
return ownKeys(o);
|
|
34
|
+
};
|
|
35
|
+
return function (mod) {
|
|
36
|
+
if (mod && mod.__esModule) return mod;
|
|
37
|
+
var result = {};
|
|
38
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
39
|
+
__setModuleDefault(result, mod);
|
|
40
|
+
return result;
|
|
41
|
+
};
|
|
42
|
+
})();
|
|
43
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
44
|
+
exports.BitwardenCredentialStore = void 0;
|
|
45
|
+
const node_child_process_1 = require("node:child_process");
|
|
46
|
+
const fs = __importStar(require("node:fs"));
|
|
47
|
+
const runtime_1 = require("../nerves/runtime");
|
|
48
|
+
const bw_installer_1 = require("./bw-installer");
|
|
49
|
+
// ---------------------------------------------------------------------------
|
|
50
|
+
// bw CLI wrapper
|
|
51
|
+
// ---------------------------------------------------------------------------
|
|
52
|
+
function isBwSessionUnavailableMessage(message) {
|
|
53
|
+
return (/master password/i.test(message) ||
|
|
54
|
+
/vault is locked/i.test(message) ||
|
|
55
|
+
/not logged in/i.test(message) ||
|
|
56
|
+
/session key/i.test(message) ||
|
|
57
|
+
/local bitwarden session/i.test(message));
|
|
58
|
+
}
|
|
59
|
+
function isBwInvalidUnlockSecretMessage(message) {
|
|
60
|
+
return /invalid master password/i.test(message) || /saved vault unlock secret/i.test(message);
|
|
61
|
+
}
|
|
62
|
+
function sanitizeBwErrorDetail(message) {
|
|
63
|
+
if (isBwInvalidUnlockSecretMessage(message)) {
|
|
64
|
+
return "bw CLI rejected the saved vault unlock secret for this machine";
|
|
65
|
+
}
|
|
66
|
+
if (isBwSessionUnavailableMessage(message)) {
|
|
67
|
+
return "bw CLI could not use the local Bitwarden session because it is locked, missing, or expired";
|
|
68
|
+
}
|
|
69
|
+
const withoutCommandLine = message
|
|
70
|
+
.split(/\r?\n/)
|
|
71
|
+
.filter((line) => !line.trim().startsWith("Command failed:"))
|
|
72
|
+
.join("\n")
|
|
73
|
+
.trim();
|
|
74
|
+
return (withoutCommandLine || "command failed")
|
|
75
|
+
.replace(/[A-Za-z0-9+/=]{80,}/g, "[redacted]")
|
|
76
|
+
.slice(0, 500);
|
|
77
|
+
}
|
|
78
|
+
function formatBwCliError(err, stderr = "") {
|
|
79
|
+
const detail = sanitizeBwErrorDetail(stderr.trim() || err.message);
|
|
80
|
+
return new Error(`bw CLI error: ${detail}`);
|
|
81
|
+
}
|
|
82
|
+
function isBwSessionAuthError(err) {
|
|
83
|
+
return isBwSessionUnavailableMessage(err.message) || isBwInvalidUnlockSecretMessage(err.message);
|
|
84
|
+
}
|
|
85
|
+
function isBwConfigLogoutRequired(err) {
|
|
86
|
+
const message = err.message.toLowerCase();
|
|
87
|
+
return message.includes("logout") && message.includes("required");
|
|
88
|
+
}
|
|
89
|
+
function execBw(args, sessionToken, appDataDir, stdin) {
|
|
90
|
+
const env = {
|
|
91
|
+
...process.env,
|
|
92
|
+
...(sessionToken ? { BW_SESSION: sessionToken } : {}),
|
|
93
|
+
...(appDataDir ? { BITWARDENCLI_APPDATA_DIR: appDataDir } : {}),
|
|
94
|
+
};
|
|
95
|
+
return new Promise((resolve, reject) => {
|
|
96
|
+
const child = (0, node_child_process_1.execFile)("bw", args, { timeout: 30_000, env }, (err, stdout, stderr) => {
|
|
97
|
+
if (err) {
|
|
98
|
+
if (isBwNotInstalled(err)) {
|
|
99
|
+
reject(new Error("bw CLI not found. Install from https://bitwarden.com/help/cli/"));
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
reject(formatBwCliError(err, stderr));
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
resolve(stdout);
|
|
106
|
+
});
|
|
107
|
+
if (stdin !== undefined) {
|
|
108
|
+
child?.stdin?.end(stdin);
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
/** Check if the error indicates the bw CLI binary is not installed. */
|
|
113
|
+
function isBwNotInstalled(err) {
|
|
114
|
+
const msg = err.message.toLowerCase();
|
|
115
|
+
const code = err.code;
|
|
116
|
+
return code === "ENOENT" || msg.includes("enoent") || msg.includes("not found") || msg.includes("command not found");
|
|
117
|
+
}
|
|
118
|
+
/** Check if the error is transient (network/timeout) and worth retrying. */
|
|
119
|
+
function isTransientError(err) {
|
|
120
|
+
const msg = err.message.toLowerCase();
|
|
121
|
+
return (msg.includes("econnrefused") ||
|
|
122
|
+
msg.includes("etimedout") ||
|
|
123
|
+
msg.includes("enotfound") ||
|
|
124
|
+
msg.includes("socket hang up") ||
|
|
125
|
+
msg.includes("503") ||
|
|
126
|
+
msg.includes("server unavailable"));
|
|
127
|
+
}
|
|
128
|
+
const MAX_RETRIES = 3;
|
|
129
|
+
const BASE_BACKOFF_MS = 1000;
|
|
130
|
+
function delay(ms) {
|
|
131
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
132
|
+
}
|
|
133
|
+
function isBwLoginItem(value) {
|
|
134
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
135
|
+
return false;
|
|
136
|
+
const item = value;
|
|
137
|
+
if (typeof item.id !== "string" || item.id.trim().length === 0)
|
|
138
|
+
return false;
|
|
139
|
+
if (typeof item.name !== "string" || item.name.trim().length === 0)
|
|
140
|
+
return false;
|
|
141
|
+
if (item.login !== undefined) {
|
|
142
|
+
if (!item.login || typeof item.login !== "object" || Array.isArray(item.login))
|
|
143
|
+
return false;
|
|
144
|
+
const login = item.login;
|
|
145
|
+
if (login.username !== undefined && typeof login.username !== "string")
|
|
146
|
+
return false;
|
|
147
|
+
if (login.password !== undefined && typeof login.password !== "string")
|
|
148
|
+
return false;
|
|
149
|
+
if (login.uris !== undefined) {
|
|
150
|
+
if (!Array.isArray(login.uris))
|
|
151
|
+
return false;
|
|
152
|
+
for (const uri of login.uris) {
|
|
153
|
+
if (!uri || typeof uri !== "object" || Array.isArray(uri))
|
|
154
|
+
return false;
|
|
155
|
+
const uriRecord = uri;
|
|
156
|
+
if (uriRecord.uri !== undefined && typeof uriRecord.uri !== "string")
|
|
157
|
+
return false;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
if (item.notes !== undefined && item.notes !== null && typeof item.notes !== "string")
|
|
162
|
+
return false;
|
|
163
|
+
if (item.revisionDate !== undefined && typeof item.revisionDate !== "string")
|
|
164
|
+
return false;
|
|
165
|
+
return true;
|
|
166
|
+
}
|
|
167
|
+
function parseBwItems(stdout, context) {
|
|
168
|
+
let parsed;
|
|
169
|
+
try {
|
|
170
|
+
parsed = JSON.parse(stdout);
|
|
171
|
+
if (!Array.isArray(parsed)) {
|
|
172
|
+
throw new Error("expected item array");
|
|
173
|
+
}
|
|
174
|
+
const items = parsed;
|
|
175
|
+
if (!items.every(isBwLoginItem)) {
|
|
176
|
+
throw new Error("expected login items");
|
|
177
|
+
}
|
|
178
|
+
return items;
|
|
179
|
+
}
|
|
180
|
+
catch {
|
|
181
|
+
if (Array.isArray(parsed)) {
|
|
182
|
+
throw new Error(`bw CLI error: invalid item from ${context}`);
|
|
183
|
+
}
|
|
184
|
+
throw new Error(`bw CLI error: invalid JSON from ${context}`);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
function parseBwItem(stdout, context) {
|
|
188
|
+
let parsed;
|
|
189
|
+
try {
|
|
190
|
+
parsed = JSON.parse(stdout);
|
|
191
|
+
if (!isBwLoginItem(parsed)) {
|
|
192
|
+
throw new Error("expected login item");
|
|
193
|
+
}
|
|
194
|
+
return parsed;
|
|
195
|
+
}
|
|
196
|
+
catch {
|
|
197
|
+
if (parsed !== undefined) {
|
|
198
|
+
throw new Error(`bw CLI error: invalid item from ${context}`);
|
|
199
|
+
}
|
|
200
|
+
throw new Error(`bw CLI error: invalid JSON from ${context}`);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
function parseBwItemId(stdout) {
|
|
204
|
+
try {
|
|
205
|
+
const parsed = JSON.parse(stdout);
|
|
206
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
207
|
+
return null;
|
|
208
|
+
const id = parsed.id;
|
|
209
|
+
return typeof id === "string" && id.trim().length > 0 ? id : null;
|
|
210
|
+
}
|
|
211
|
+
catch {
|
|
212
|
+
return null;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
// ---------------------------------------------------------------------------
|
|
216
|
+
// BitwardenCredentialStore
|
|
217
|
+
// ---------------------------------------------------------------------------
|
|
218
|
+
class BitwardenCredentialStore {
|
|
219
|
+
serverUrl;
|
|
220
|
+
email;
|
|
221
|
+
masterPassword;
|
|
222
|
+
appDataDir;
|
|
223
|
+
sessionToken = null;
|
|
224
|
+
constructor(serverUrl, email, masterPassword, options = {}) {
|
|
225
|
+
this.serverUrl = serverUrl;
|
|
226
|
+
this.email = email;
|
|
227
|
+
this.masterPassword = masterPassword;
|
|
228
|
+
this.appDataDir = options.appDataDir;
|
|
229
|
+
}
|
|
230
|
+
isReady() {
|
|
231
|
+
return true;
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Ensure the bw CLI is authenticated and unlocked.
|
|
235
|
+
* Handles three states: logged out → login, locked → unlock, already unlocked → no-op.
|
|
236
|
+
* Retries transient failures (network/timeout) up to MAX_RETRIES with exponential backoff.
|
|
237
|
+
*/
|
|
238
|
+
async login() {
|
|
239
|
+
// Ensure bw CLI is installed before any bw commands
|
|
240
|
+
await (0, bw_installer_1.ensureBwCli)();
|
|
241
|
+
if (this.appDataDir) {
|
|
242
|
+
fs.mkdirSync(this.appDataDir, { recursive: true, mode: 0o700 });
|
|
243
|
+
}
|
|
244
|
+
let lastError;
|
|
245
|
+
for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {
|
|
246
|
+
try {
|
|
247
|
+
await this.loginAttempt();
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
catch (err) {
|
|
251
|
+
/* v8 ignore next -- defensive: loginAttempt always throws Error instances @preserve */
|
|
252
|
+
lastError = err instanceof Error ? err : new Error(String(err));
|
|
253
|
+
// Don't retry non-transient errors (auth failures, bw not installed)
|
|
254
|
+
if (!isTransientError(lastError)) {
|
|
255
|
+
throw lastError;
|
|
256
|
+
}
|
|
257
|
+
// Don't retry after final attempt
|
|
258
|
+
if (attempt === MAX_RETRIES - 1)
|
|
259
|
+
break;
|
|
260
|
+
const backoffMs = BASE_BACKOFF_MS * Math.pow(2, attempt);
|
|
261
|
+
(0, runtime_1.emitNervesEvent)({
|
|
262
|
+
event: "repertoire.bw_login_retry",
|
|
263
|
+
component: "repertoire",
|
|
264
|
+
message: `bw login attempt ${attempt + 1} failed, retrying in ${backoffMs}ms`,
|
|
265
|
+
meta: { attempt: attempt + 1, backoffMs, reason: lastError.message },
|
|
266
|
+
});
|
|
267
|
+
await delay(backoffMs);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
throw lastError;
|
|
271
|
+
}
|
|
272
|
+
/** Single login attempt — called by login() retry loop. */
|
|
273
|
+
async loginAttempt() {
|
|
274
|
+
// Check current status
|
|
275
|
+
let status = {};
|
|
276
|
+
try {
|
|
277
|
+
const raw = await execBw(["status"], undefined, this.appDataDir);
|
|
278
|
+
status = JSON.parse(raw);
|
|
279
|
+
}
|
|
280
|
+
catch (err) {
|
|
281
|
+
// If bw CLI is not installed or a transient error, propagate it for retry
|
|
282
|
+
if (err instanceof Error && (isBwNotInstalled(err) || isTransientError(err))) {
|
|
283
|
+
throw err;
|
|
284
|
+
}
|
|
285
|
+
// CLI not configured or broken — proceed with full setup
|
|
286
|
+
}
|
|
287
|
+
// Configure server URL if needed (only works when logged out)
|
|
288
|
+
if (status.status === "unauthenticated" || !status.serverUrl) {
|
|
289
|
+
try {
|
|
290
|
+
await execBw(["config", "server", this.serverUrl], undefined, this.appDataDir);
|
|
291
|
+
}
|
|
292
|
+
catch (error) {
|
|
293
|
+
const err = error;
|
|
294
|
+
// "Logout required" means already logged in — that's fine, skip config.
|
|
295
|
+
if (!isBwConfigLogoutRequired(err)) {
|
|
296
|
+
throw err;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
if (status.status === "locked") {
|
|
301
|
+
// Already logged in, just needs unlock
|
|
302
|
+
const unlockOutput = await execBw(["unlock", this.masterPassword, "--raw"], undefined, this.appDataDir);
|
|
303
|
+
this.sessionToken = unlockOutput.trim();
|
|
304
|
+
}
|
|
305
|
+
else if (status.status === "unauthenticated" || !status.status) {
|
|
306
|
+
// Not logged in — full login
|
|
307
|
+
const loginOutput = await execBw(["login", this.email, this.masterPassword, "--raw"], undefined, this.appDataDir);
|
|
308
|
+
try {
|
|
309
|
+
const parsed = JSON.parse(loginOutput);
|
|
310
|
+
this.sessionToken = parsed.access_token ?? loginOutput.trim();
|
|
311
|
+
}
|
|
312
|
+
catch {
|
|
313
|
+
this.sessionToken = loginOutput.trim();
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
else {
|
|
317
|
+
// Status is "unlocked" — already good, just need the session token
|
|
318
|
+
const unlockOutput = await execBw(["unlock", this.masterPassword, "--raw"], undefined, this.appDataDir);
|
|
319
|
+
this.sessionToken = unlockOutput.trim();
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
async ensureSession() {
|
|
323
|
+
if (!this.sessionToken) {
|
|
324
|
+
await this.login();
|
|
325
|
+
}
|
|
326
|
+
/* v8 ignore next -- defensive: login() always sets sessionToken on success @preserve */
|
|
327
|
+
return this.sessionToken ?? undefined;
|
|
328
|
+
}
|
|
329
|
+
async withSessionRetry(operation) {
|
|
330
|
+
let attemptedFreshSession = false;
|
|
331
|
+
while (true) {
|
|
332
|
+
const session = await this.ensureSession();
|
|
333
|
+
try {
|
|
334
|
+
return await operation(session);
|
|
335
|
+
}
|
|
336
|
+
catch (error) {
|
|
337
|
+
const err = error;
|
|
338
|
+
if (attemptedFreshSession || !isBwSessionAuthError(err)) {
|
|
339
|
+
throw err;
|
|
340
|
+
}
|
|
341
|
+
this.sessionToken = null;
|
|
342
|
+
attemptedFreshSession = true;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
async get(domain) {
|
|
347
|
+
(0, runtime_1.emitNervesEvent)({
|
|
348
|
+
event: "repertoire.bw_credential_get_start",
|
|
349
|
+
component: "repertoire",
|
|
350
|
+
message: `getting credential via bw for ${domain}`,
|
|
351
|
+
meta: { domain, backend: "bitwarden" },
|
|
352
|
+
});
|
|
353
|
+
const item = await this.withSessionRetry((session) => this.findItemByDomain(domain, session));
|
|
354
|
+
if (!item) {
|
|
355
|
+
(0, runtime_1.emitNervesEvent)({
|
|
356
|
+
event: "repertoire.bw_credential_get_end",
|
|
357
|
+
component: "repertoire",
|
|
358
|
+
message: `no bw credential for ${domain}`,
|
|
359
|
+
meta: { domain, found: false, backend: "bitwarden" },
|
|
360
|
+
});
|
|
361
|
+
return null;
|
|
362
|
+
}
|
|
363
|
+
(0, runtime_1.emitNervesEvent)({
|
|
364
|
+
event: "repertoire.bw_credential_get_end",
|
|
365
|
+
component: "repertoire",
|
|
366
|
+
message: `bw credential found for ${domain}`,
|
|
367
|
+
meta: { domain, found: true, backend: "bitwarden" },
|
|
368
|
+
});
|
|
369
|
+
return {
|
|
370
|
+
domain: item.name,
|
|
371
|
+
username: item.login?.username,
|
|
372
|
+
notes: item.notes ?? undefined,
|
|
373
|
+
createdAt: item.revisionDate ?? new Date().toISOString(),
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
async getRawSecret(domain, field) {
|
|
377
|
+
const item = await this.withSessionRetry((session) => this.findItemByDomain(domain, session));
|
|
378
|
+
if (!item) {
|
|
379
|
+
throw new Error(`no credential found for domain "${domain}"`);
|
|
380
|
+
}
|
|
381
|
+
// Map common field names to bw item structure
|
|
382
|
+
let value;
|
|
383
|
+
if (field === "password") {
|
|
384
|
+
value = item.login?.password;
|
|
385
|
+
}
|
|
386
|
+
else if (field === "username") {
|
|
387
|
+
value = item.login?.username;
|
|
388
|
+
}
|
|
389
|
+
else {
|
|
390
|
+
value = item[field];
|
|
391
|
+
}
|
|
392
|
+
if (value === undefined || value === null) {
|
|
393
|
+
throw new Error(`field "${field}" not found for domain "${domain}"`);
|
|
394
|
+
}
|
|
395
|
+
return String(value);
|
|
396
|
+
}
|
|
397
|
+
async store(domain, data) {
|
|
398
|
+
(0, runtime_1.emitNervesEvent)({
|
|
399
|
+
event: "repertoire.bw_credential_store_start",
|
|
400
|
+
component: "repertoire",
|
|
401
|
+
message: `storing credential via bw for ${domain}`,
|
|
402
|
+
meta: { domain, backend: "bitwarden" },
|
|
403
|
+
});
|
|
404
|
+
await this.withSessionRetry(async (session) => {
|
|
405
|
+
const existing = await this.findItemByDomain(domain, session);
|
|
406
|
+
const item = {
|
|
407
|
+
...(existing ?? {}),
|
|
408
|
+
type: 1, // Login type
|
|
409
|
+
name: domain,
|
|
410
|
+
login: {
|
|
411
|
+
username: data.username ?? "",
|
|
412
|
+
password: data.password,
|
|
413
|
+
uris: [{ match: null, uri: `https://${domain}` }],
|
|
414
|
+
},
|
|
415
|
+
notes: data.notes ?? null,
|
|
416
|
+
};
|
|
417
|
+
const encoded = Buffer.from(JSON.stringify(item)).toString("base64");
|
|
418
|
+
let savedItem;
|
|
419
|
+
if (existing) {
|
|
420
|
+
const stdout = await execBw(["edit", "item", existing.id], session, this.appDataDir, encoded);
|
|
421
|
+
const savedItemId = parseBwItemId(stdout) ?? existing.id;
|
|
422
|
+
savedItem = await this.findItemById(savedItemId, session);
|
|
423
|
+
}
|
|
424
|
+
else {
|
|
425
|
+
const stdout = await execBw(["create", "item"], session, this.appDataDir, encoded);
|
|
426
|
+
const savedItemId = parseBwItemId(stdout);
|
|
427
|
+
savedItem = savedItemId
|
|
428
|
+
? await this.findItemById(savedItemId, session)
|
|
429
|
+
: await this.findItemByDomain(domain, session);
|
|
430
|
+
}
|
|
431
|
+
this.assertStoredCredentialMatches(domain, data, savedItem);
|
|
432
|
+
});
|
|
433
|
+
(0, runtime_1.emitNervesEvent)({
|
|
434
|
+
event: "repertoire.bw_credential_store_end",
|
|
435
|
+
component: "repertoire",
|
|
436
|
+
message: `credential stored via bw for ${domain}`,
|
|
437
|
+
meta: { domain, backend: "bitwarden" },
|
|
438
|
+
});
|
|
439
|
+
}
|
|
440
|
+
async list() {
|
|
441
|
+
(0, runtime_1.emitNervesEvent)({
|
|
442
|
+
event: "repertoire.bw_credential_list_start",
|
|
443
|
+
component: "repertoire",
|
|
444
|
+
message: "listing bw credentials",
|
|
445
|
+
meta: { backend: "bitwarden" },
|
|
446
|
+
});
|
|
447
|
+
const stdout = await this.withSessionRetry((session) => execBw(["list", "items"], session, this.appDataDir));
|
|
448
|
+
const items = parseBwItems(stdout, "bw list items");
|
|
449
|
+
const results = items.map((item) => ({
|
|
450
|
+
domain: item.name,
|
|
451
|
+
username: item.login?.username,
|
|
452
|
+
notes: item.notes ?? undefined,
|
|
453
|
+
createdAt: item.revisionDate ?? new Date().toISOString(),
|
|
454
|
+
}));
|
|
455
|
+
(0, runtime_1.emitNervesEvent)({
|
|
456
|
+
event: "repertoire.bw_credential_list_end",
|
|
457
|
+
component: "repertoire",
|
|
458
|
+
message: "bw credentials listed",
|
|
459
|
+
meta: { backend: "bitwarden", count: results.length },
|
|
460
|
+
});
|
|
461
|
+
return results;
|
|
462
|
+
}
|
|
463
|
+
async delete(domain) {
|
|
464
|
+
(0, runtime_1.emitNervesEvent)({
|
|
465
|
+
event: "repertoire.bw_credential_delete_start",
|
|
466
|
+
component: "repertoire",
|
|
467
|
+
message: `deleting credential via bw for ${domain}`,
|
|
468
|
+
meta: { domain, backend: "bitwarden" },
|
|
469
|
+
});
|
|
470
|
+
const item = await this.withSessionRetry((session) => this.findItemByDomain(domain, session));
|
|
471
|
+
if (!item) {
|
|
472
|
+
(0, runtime_1.emitNervesEvent)({
|
|
473
|
+
event: "repertoire.bw_credential_delete_end",
|
|
474
|
+
component: "repertoire",
|
|
475
|
+
message: `no bw credential to delete for ${domain}`,
|
|
476
|
+
meta: { domain, deleted: false, backend: "bitwarden" },
|
|
477
|
+
});
|
|
478
|
+
return false;
|
|
479
|
+
}
|
|
480
|
+
await this.withSessionRetry((session) => execBw(["delete", "item", item.id], session, this.appDataDir));
|
|
481
|
+
(0, runtime_1.emitNervesEvent)({
|
|
482
|
+
event: "repertoire.bw_credential_delete_end",
|
|
483
|
+
component: "repertoire",
|
|
484
|
+
message: `credential deleted via bw for ${domain}`,
|
|
485
|
+
meta: { domain, deleted: true, backend: "bitwarden" },
|
|
486
|
+
});
|
|
487
|
+
return true;
|
|
488
|
+
}
|
|
489
|
+
// --- Private ---
|
|
490
|
+
async findItemByDomain(domain, session) {
|
|
491
|
+
const stdout = await execBw(["list", "items", "--search", domain], session, this.appDataDir);
|
|
492
|
+
const items = parseBwItems(stdout, "bw list items --search");
|
|
493
|
+
// Find exact match by name
|
|
494
|
+
return items.find((item) => item.name === domain) ?? null;
|
|
495
|
+
}
|
|
496
|
+
async findItemById(id, session) {
|
|
497
|
+
const stdout = await execBw(["get", "item", id], session, this.appDataDir);
|
|
498
|
+
return parseBwItem(stdout, "bw get item");
|
|
499
|
+
}
|
|
500
|
+
assertStoredCredentialMatches(domain, data, item) {
|
|
501
|
+
if (!item) {
|
|
502
|
+
throw new Error(`bw CLI error: credential save verification failed for ${domain}: saved item could not be read back after write`);
|
|
503
|
+
}
|
|
504
|
+
const mismatches = [];
|
|
505
|
+
if (item.name !== domain)
|
|
506
|
+
mismatches.push("name");
|
|
507
|
+
if ((item.login?.username ?? "") !== (data.username ?? ""))
|
|
508
|
+
mismatches.push("username");
|
|
509
|
+
if ((item.login?.password ?? "") !== data.password)
|
|
510
|
+
mismatches.push("password");
|
|
511
|
+
if ((item.notes ?? null) !== (data.notes ?? null))
|
|
512
|
+
mismatches.push("notes");
|
|
513
|
+
if (mismatches.length > 0) {
|
|
514
|
+
const label = mismatches.length === 1 ? "field" : "fields";
|
|
515
|
+
throw new Error(`bw CLI error: credential save verification failed for ${domain}: saved item did not match requested ${label} ${mismatches.join(", ")}`);
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
exports.BitwardenCredentialStore = BitwardenCredentialStore;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Templates for agent bundle scaffolding.
|
|
4
|
+
*
|
|
5
|
+
* ## .gitignore design philosophy
|
|
6
|
+
*
|
|
7
|
+
* The bundle .gitignore handles FUNCTIONAL "shouldn't track" cases only:
|
|
8
|
+
*
|
|
9
|
+
* - Runtime state (sessions, logs, runtime files) — stale data with no
|
|
10
|
+
* value for review or history.
|
|
11
|
+
* - Credentials — real secrets live in the agent vault, but defense
|
|
12
|
+
* in depth in case anything leaks into the bundle.
|
|
13
|
+
* - Editor / OS noise (.DS_Store, .idea/, etc.).
|
|
14
|
+
* - Build artifacts (rare in bundles, but possible).
|
|
15
|
+
*
|
|
16
|
+
* It DOES NOT handle PII. The bundle is inherently full of PII — `friends/`,
|
|
17
|
+
* `diary/`, `journal/`, `psyche/`, `arc/`, `facts/`, `family/`, `travel/`
|
|
18
|
+
* etc. That's the point of the bundle; blocking those via .gitignore would
|
|
19
|
+
* defeat the purpose.
|
|
20
|
+
*
|
|
21
|
+
* PII is handled at first-push time by `bundle_first_push_review`, which
|
|
22
|
+
* enumerates PII-bearing directories, shows the agent counts, probes the
|
|
23
|
+
* remote URL for GitHub visibility, and hard-pauses until the human
|
|
24
|
+
* confirms. See Directive D in the planning doc.
|
|
25
|
+
*
|
|
26
|
+
* No content-pattern blocks (no `**\/sk-ant-*` or similar). Content-review
|
|
27
|
+
* failures are a different safety layer — credential scanning at commit
|
|
28
|
+
* time would be a follow-up feature.
|
|
29
|
+
*/
|
|
30
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
31
|
+
exports.PII_BUNDLE_DIRECTORIES = exports.BUNDLE_GITIGNORE_TEMPLATE = void 0;
|
|
32
|
+
exports.BUNDLE_GITIGNORE_TEMPLATE = `# Runtime state — sessions, logs, runtime files, never tracked
|
|
33
|
+
state/
|
|
34
|
+
|
|
35
|
+
# Credentials — never tracked. Real secrets live in the agent vault, but
|
|
36
|
+
# defense in depth in case anything leaks into the bundle.
|
|
37
|
+
.env
|
|
38
|
+
.env.*
|
|
39
|
+
secrets/
|
|
40
|
+
**/*.key
|
|
41
|
+
**/*.pem
|
|
42
|
+
**/*.credentials
|
|
43
|
+
**/*.pfx
|
|
44
|
+
|
|
45
|
+
# Editor and OS noise
|
|
46
|
+
.DS_Store
|
|
47
|
+
.idea/
|
|
48
|
+
.vscode/
|
|
49
|
+
*.swp
|
|
50
|
+
*.swo
|
|
51
|
+
|
|
52
|
+
# Build artifacts (rare in bundles, but possible if a workspace lands here)
|
|
53
|
+
node_modules/
|
|
54
|
+
dist/
|
|
55
|
+
`;
|
|
56
|
+
/**
|
|
57
|
+
* PII-sensitive top-level directories. Enumerated here so `bundle_first_push_review`
|
|
58
|
+
* can categorize and count. Adding a new PII bucket to the bundle means adding
|
|
59
|
+
* it here so the first-push warning includes it.
|
|
60
|
+
*/
|
|
61
|
+
exports.PII_BUNDLE_DIRECTORIES = [
|
|
62
|
+
"friends",
|
|
63
|
+
"diary",
|
|
64
|
+
"journal",
|
|
65
|
+
"psyche",
|
|
66
|
+
"arc",
|
|
67
|
+
"facts",
|
|
68
|
+
"family",
|
|
69
|
+
"travel",
|
|
70
|
+
"notes",
|
|
71
|
+
"sessions",
|
|
72
|
+
];
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Lazy bw CLI installer — auto-installs the Bitwarden CLI when not present.
|
|
4
|
+
*
|
|
5
|
+
* Mirrors the whisper-cpp pattern in senses/bluebubbles/media.ts:
|
|
6
|
+
* check PATH first, install via npm if missing, emit nerves event.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.ensureBwCli = ensureBwCli;
|
|
10
|
+
const node_child_process_1 = require("node:child_process");
|
|
11
|
+
const runtime_1 = require("../nerves/runtime");
|
|
12
|
+
const INSTALL_TIMEOUT_MS = 120_000;
|
|
13
|
+
const WHICH_TIMEOUT_MS = 5_000;
|
|
14
|
+
function execFileAsync(cmd, args, timeout) {
|
|
15
|
+
return new Promise((resolve, reject) => {
|
|
16
|
+
(0, node_child_process_1.execFile)(cmd, args, { timeout }, (err, stdout) => {
|
|
17
|
+
if (err) {
|
|
18
|
+
reject(err);
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
resolve(stdout);
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Ensure the `bw` CLI is available, installing it via npm if needed.
|
|
27
|
+
* Returns the path to the `bw` binary.
|
|
28
|
+
*/
|
|
29
|
+
async function ensureBwCli() {
|
|
30
|
+
// 1. Check if bw is already in PATH
|
|
31
|
+
try {
|
|
32
|
+
const existing = (await execFileAsync("which", ["bw"], WHICH_TIMEOUT_MS)).trim();
|
|
33
|
+
if (existing) {
|
|
34
|
+
return existing;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
// Not found — fall through to install
|
|
39
|
+
}
|
|
40
|
+
// 2. Install via npm
|
|
41
|
+
(0, runtime_1.emitNervesEvent)({
|
|
42
|
+
event: "repertoire.bw_cli_install_start",
|
|
43
|
+
component: "repertoire",
|
|
44
|
+
message: "bw CLI not found, installing via npm",
|
|
45
|
+
meta: {},
|
|
46
|
+
});
|
|
47
|
+
try {
|
|
48
|
+
await execFileAsync("npm", ["install", "-g", "@bitwarden/cli"], INSTALL_TIMEOUT_MS);
|
|
49
|
+
}
|
|
50
|
+
catch (err) {
|
|
51
|
+
/* v8 ignore next -- execFileCb always throws Error instances @preserve */
|
|
52
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
53
|
+
(0, runtime_1.emitNervesEvent)({
|
|
54
|
+
level: "error",
|
|
55
|
+
event: "repertoire.bw_cli_install_fail",
|
|
56
|
+
component: "repertoire",
|
|
57
|
+
message: "failed to install bw CLI via npm",
|
|
58
|
+
meta: { reason },
|
|
59
|
+
});
|
|
60
|
+
throw new Error(`failed to install bw CLI via npm: ${reason}`);
|
|
61
|
+
}
|
|
62
|
+
// 3. Verify installation and return path
|
|
63
|
+
try {
|
|
64
|
+
const installed = (await execFileAsync("which", ["bw"], WHICH_TIMEOUT_MS)).trim();
|
|
65
|
+
if (installed) {
|
|
66
|
+
(0, runtime_1.emitNervesEvent)({
|
|
67
|
+
event: "repertoire.bw_cli_install_end",
|
|
68
|
+
component: "repertoire",
|
|
69
|
+
message: "bw CLI installed successfully",
|
|
70
|
+
meta: { path: installed },
|
|
71
|
+
});
|
|
72
|
+
return installed;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
// Fall through to error
|
|
77
|
+
}
|
|
78
|
+
throw new Error("bw CLI installed via npm but binary not found in PATH");
|
|
79
|
+
}
|