aidevops 3.22.4 → 3.22.5
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 +13 -1
- package/VERSION +1 -1
- package/aidevops.sh +11 -1
- package/package.json +1 -1
- package/packages/gui-api/package.json +3 -0
- package/packages/gui-api/src/app.ts +6 -2
- package/packages/gui-api/src/status-adapter.ts +29 -1
- package/packages/gui-api/src/status-vault.ts +110 -0
- package/packages/gui-shared/package.json +3 -0
- package/packages/gui-shared/src/contracts.ts +84 -2
- package/packages/gui-shared/src/fixtures.ts +101 -0
- package/packages/gui-web/package.json +2 -1
- package/packages/gui-web/src/App.tsx +2 -0
- package/packages/gui-web/src/AppNavigation.tsx +14 -6
- package/packages/gui-web/src/AppWorkspace.tsx +11 -3
- package/packages/gui-web/src/StatusSurfaces.tsx +151 -1
- package/packages/gui-web/src/VaultBadges.tsx +39 -0
- package/packages/gui-web/src/app-model.ts +22 -2
- package/packages/gui-web/src/dashboard.ts +6 -1
- package/packages/gui-web/src/status-client.ts +11 -0
- package/packages/gui-web/src/styles.css +103 -3
- package/setup.sh +1 -1
package/README.md
CHANGED
|
@@ -58,7 +58,7 @@ The result: an AI operations platform that manages projects across every busines
|
|
|
58
58
|
[](https://github.com/marcusquinn)
|
|
59
59
|
|
|
60
60
|
<!-- Release & Version Info -->
|
|
61
|
-
[](https://github.com/marcusquinn/aidevops/releases)
|
|
62
62
|
[](https://www.npmjs.com/package/aidevops)
|
|
63
63
|
[](https://github.com/marcusquinn/homebrew-tap)
|
|
64
64
|
[](https://github.com/marcusquinn/aidevops)
|
|
@@ -129,6 +129,18 @@ Since the last README feature refresh, aidevops has added or expanded:
|
|
|
129
129
|
|
|
130
130
|
**Comprehensive DevOps framework with tried & tested services integrations, popular and trusted MCP servers, and enterprise-grade infrastructure quality assurance code monitoring and recommendations.**
|
|
131
131
|
|
|
132
|
+
**Vault security model:** aidevops defines protected data classes, provider
|
|
133
|
+
routing labels, trust boundaries, and phased encrypted sync architecture in
|
|
134
|
+
`.agents/reference/vault.md`. Vault guidance is local-first: third-party AI
|
|
135
|
+
providers can only reason over data that has been decrypted into their prompt or
|
|
136
|
+
tool context, so provider-side logs/retention remain outside Vault's technical
|
|
137
|
+
control. Future local LLM mode reduces provider exposure but not local host
|
|
138
|
+
compromise risk.
|
|
139
|
+
|
|
140
|
+
**Vault passphrase warning:** aidevops cannot recover a lost Vault passphrase.
|
|
141
|
+
Save it in a trusted password manager with backups; never paste it into AI chat,
|
|
142
|
+
CLI arguments, environment variables, logs, issue comments, or test fixtures.
|
|
143
|
+
|
|
132
144
|
### Report creation, previews, and PDF exports
|
|
133
145
|
|
|
134
146
|
Use aidevops to turn evidence bundles into decision-ready reports while keeping Markdown or JSON as the canonical source. Report agents can produce AI-search audits, SEO/GEO scorecards, delivery reviews, campaign reports, board packs, incident summaries, recurring client handoffs, and before/after remediation evidence.
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
3.22.
|
|
1
|
+
3.22.5
|
package/aidevops.sh
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
# AI DevOps Framework CLI
|
|
6
6
|
# Usage: aidevops <command> [options]
|
|
7
7
|
#
|
|
8
|
-
# Version: 3.22.
|
|
8
|
+
# Version: 3.22.5
|
|
9
9
|
|
|
10
10
|
set -euo pipefail
|
|
11
11
|
|
|
@@ -861,6 +861,7 @@ _help_commands() {
|
|
|
861
861
|
echo " review-gate <cmd> Configure review_gate merge policies (rate-limit/completion)"
|
|
862
862
|
echo " github-app-auth GitHub App auth setup/status and API route decisions"
|
|
863
863
|
echo " secret <cmd> Manage secrets (set/list/run/init/import/status)"
|
|
864
|
+
echo " vault <cmd> Local encrypted Vault broker (init/unlock/lock/status/read/update)"
|
|
864
865
|
echo " config <cmd> Feature toggles (list/get/set/reset/path/help)"
|
|
865
866
|
echo " knowledge <cmd> Knowledge plane management (init/status/provision)"
|
|
866
867
|
echo " campaign <cmd> Campaign plane: init/provision/ls (P1) + asset (P4) + new/list/status/archive (P2) + draft (P5) + launch/promote (P6)"
|
|
@@ -937,6 +938,14 @@ _help_detailed_sections() {
|
|
|
937
938
|
echo " aidevops secret import # Import from credentials.sh to gopass"
|
|
938
939
|
echo " aidevops secret status # Show backend status"
|
|
939
940
|
echo ""
|
|
941
|
+
echo "Vault:"
|
|
942
|
+
echo " aidevops vault init # Create local encrypted Vault metadata"
|
|
943
|
+
echo " aidevops vault status # Show uninitialized/locked/unlocked/corrupted"
|
|
944
|
+
echo " aidevops vault unlock # Unlock into an in-memory local broker"
|
|
945
|
+
echo " aidevops vault lock # Stop broker and forget in-memory keys"
|
|
946
|
+
echo " aidevops vault read NAME # Read protected data through broker"
|
|
947
|
+
echo " aidevops vault update NAME # Encrypt stdin value through broker"
|
|
948
|
+
echo ""
|
|
940
949
|
echo "GitHub App Auth:"
|
|
941
950
|
echo " aidevops github-app-auth status --json # Show active auth mode and budgets"
|
|
942
951
|
echo " aidevops github-app-auth route issue-list # Explain route decision"
|
|
@@ -1631,6 +1640,7 @@ main() {
|
|
|
1631
1640
|
opencode-sandbox | oc-sandbox) _dispatch_helper "opencode-sandbox-helper.sh" "opencode-sandbox-helper.sh" "$@" ;;
|
|
1632
1641
|
review-gate | review_gate) _dispatch_helper "review-gate-config-helper.sh" "review-gate-config-helper.sh" "$@" ;;
|
|
1633
1642
|
secret | secrets) _dispatch_helper "secret-helper.sh" "secret-helper.sh" "$@" ;;
|
|
1643
|
+
vault) _dispatch_helper "vault-helper.sh" "vault-helper.sh" "$@" ;;
|
|
1634
1644
|
approve) _dispatch_helper "approval-helper.sh" "approval-helper.sh" "$@" ;;
|
|
1635
1645
|
circuit-breaker | circuit_breaker | cb)
|
|
1636
1646
|
# Supervisor circuit breaker control (t1331). Bare invocation defaults to status.
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Hono } from "hono";
|
|
2
|
-
import { BANNED_ROUTE_PATTERNS, FILE_EXPLORER_ROUTE_MANIFEST, STATUS_ROUTE_MANIFEST } from "../../gui-shared/src";
|
|
2
|
+
import { BANNED_ROUTE_PATTERNS, FILE_EXPLORER_ROUTE_MANIFEST, STATUS_ROUTE_MANIFEST, VAULT_STATUS_ROUTE_MANIFEST } from "../../gui-shared/src";
|
|
3
3
|
import { readFileExplorer } from "./file-adapter";
|
|
4
|
-
import { readStatus } from "./status-adapter";
|
|
4
|
+
import { readStatus, readVaultStatus } from "./status-adapter";
|
|
5
5
|
|
|
6
6
|
export function createGuiApiApp() {
|
|
7
7
|
const app = new Hono();
|
|
@@ -10,6 +10,10 @@ export function createGuiApiApp() {
|
|
|
10
10
|
return context.json(readStatus());
|
|
11
11
|
});
|
|
12
12
|
|
|
13
|
+
app.get(VAULT_STATUS_ROUTE_MANIFEST.route, (context) => {
|
|
14
|
+
return context.json(readVaultStatus());
|
|
15
|
+
});
|
|
16
|
+
|
|
13
17
|
app.get(FILE_EXPLORER_ROUTE_MANIFEST.route, (context) => {
|
|
14
18
|
const root = context.req.param("root");
|
|
15
19
|
const path = context.req.query("path") ?? "";
|
|
@@ -5,6 +5,7 @@ import { join } from "node:path";
|
|
|
5
5
|
import {
|
|
6
6
|
assertNoSecretSentinels,
|
|
7
7
|
createEnvelope,
|
|
8
|
+
VAULT_STATUS_ROUTE_MANIFEST,
|
|
8
9
|
type GuiAiAppSummary,
|
|
9
10
|
type GuiAiProviderId,
|
|
10
11
|
type GuiOAuthPoolSummary,
|
|
@@ -15,6 +16,7 @@ import {
|
|
|
15
16
|
type GuiSettingsSummary,
|
|
16
17
|
type GuiSetupTargetSummary,
|
|
17
18
|
type GuiStatusData,
|
|
19
|
+
type GuiVaultStatusData,
|
|
18
20
|
statusFixture,
|
|
19
21
|
} from "../../gui-shared/src";
|
|
20
22
|
import {
|
|
@@ -30,6 +32,9 @@ import {
|
|
|
30
32
|
stringField,
|
|
31
33
|
} from "./status-adapter-utils";
|
|
32
34
|
import { readLocalReposSetupSummary } from "./status-local-repos";
|
|
35
|
+
import { readVaultSummary } from "./status-vault";
|
|
36
|
+
|
|
37
|
+
export { readVaultSummary } from "./status-vault";
|
|
33
38
|
|
|
34
39
|
export interface StatusAdapterOptions {
|
|
35
40
|
repoRoot?: string;
|
|
@@ -47,6 +52,7 @@ export function readStatus(
|
|
|
47
52
|
const reposPathRef = "~/.config/aidevops/repos.json";
|
|
48
53
|
const oauthPoolPathRef = "~/.aidevops/oauth-pool.json";
|
|
49
54
|
const agentsPathRef = "~/.aidevops/agents";
|
|
55
|
+
const vaultPathRef = statusFixture.vault.path_ref;
|
|
50
56
|
const settingsPath = expandHome(settingsPathRef);
|
|
51
57
|
const reposPath = expandHome(reposPathRef);
|
|
52
58
|
const oauthPoolPath = expandHome(oauthPoolPathRef);
|
|
@@ -57,11 +63,13 @@ export function readStatus(
|
|
|
57
63
|
const restartRequired = installedVersion !== aidevopsVersion;
|
|
58
64
|
const setupTargets = readSetupTargets(aidevopsVersion || "unknown", installedVersion || "unknown");
|
|
59
65
|
const aiApps = readAiApps(aidevopsVersion || "unknown", installedVersion || "unknown");
|
|
66
|
+
const vault = readVaultSummary(repoRoot);
|
|
60
67
|
const sourcePathRefs = [
|
|
61
68
|
agentsPathRef,
|
|
62
69
|
settingsPathRef,
|
|
63
70
|
reposPathRef,
|
|
64
71
|
oauthPoolPathRef,
|
|
72
|
+
vaultPathRef,
|
|
65
73
|
"VERSION",
|
|
66
74
|
...setupTargets.map((target) => target.path_ref),
|
|
67
75
|
...aiApps.flatMap((app) => [app.app_path_ref, app.binary_path_ref, app.config_path_ref, app.aidevops_target_path_ref]),
|
|
@@ -110,6 +118,7 @@ export function readStatus(
|
|
|
110
118
|
oauth_pool: readOAuthPoolSummary(oauthPoolPath, oauthPoolPathRef),
|
|
111
119
|
setup_targets: setupTargets,
|
|
112
120
|
ai_apps: aiApps,
|
|
121
|
+
vault,
|
|
113
122
|
};
|
|
114
123
|
|
|
115
124
|
const envelope = createEnvelope({
|
|
@@ -128,8 +137,27 @@ export function readStatus(
|
|
|
128
137
|
return envelope;
|
|
129
138
|
}
|
|
130
139
|
|
|
131
|
-
|
|
140
|
+
export function readVaultStatus(
|
|
141
|
+
options: StatusAdapterOptions = {},
|
|
142
|
+
): GuiResponseEnvelope<GuiVaultStatusData> {
|
|
143
|
+
const data = readVaultSummary(options.repoRoot ?? process.cwd());
|
|
144
|
+
const envelope = createEnvelope({
|
|
145
|
+
operation_id: VAULT_STATUS_ROUTE_MANIFEST.operation_id,
|
|
146
|
+
source: {
|
|
147
|
+
surface: "vault",
|
|
148
|
+
authority: "aidevops vault helper",
|
|
149
|
+
path_refs: [data.path_ref],
|
|
150
|
+
},
|
|
151
|
+
data,
|
|
152
|
+
warnings: ["Vault status exposes metadata only; passphrases, keys, recovery material, and payloads are never returned."],
|
|
153
|
+
observed_at: options.observedAt,
|
|
154
|
+
});
|
|
132
155
|
|
|
156
|
+
assertNoSecretSentinels(envelope);
|
|
157
|
+
return envelope;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const AI_PROVIDER_IDS: GuiAiProviderId[] = ["anthropic", "openai", "cursor", "google"];
|
|
133
161
|
const SETUP_TARGET_DEFINITIONS: Array<Pick<GuiSetupTargetSummary, "label" | "path_ref" | "purpose">> = [
|
|
134
162
|
{
|
|
135
163
|
label: "Deployed agents",
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import {
|
|
5
|
+
type GuiVaultSetupState,
|
|
6
|
+
type GuiVaultStatus,
|
|
7
|
+
type GuiVaultStatusData,
|
|
8
|
+
statusFixture,
|
|
9
|
+
} from "../../gui-shared/src";
|
|
10
|
+
|
|
11
|
+
const GUI_VAULT_STATUSES = ["uninitialized", "locked", "unlocked", "corrupted", "unknown"] as const satisfies readonly GuiVaultStatus[];
|
|
12
|
+
const GUI_VAULT_SETUP_STATES = [
|
|
13
|
+
"uninitialized",
|
|
14
|
+
"test-created",
|
|
15
|
+
"restart-required",
|
|
16
|
+
"test-verified",
|
|
17
|
+
"migration-ready",
|
|
18
|
+
"unknown",
|
|
19
|
+
] as const satisfies readonly GuiVaultSetupState[];
|
|
20
|
+
const INITIALIZED_VAULT_STATUSES = new Set<GuiVaultStatus>(["locked", "unlocked", "corrupted"]);
|
|
21
|
+
const RESTART_TEST_SETUP_STATES = new Set<GuiVaultSetupState>(["test-created", "restart-required", "test-verified"]);
|
|
22
|
+
|
|
23
|
+
export function readVaultSummary(repoRoot: string): GuiVaultStatusData {
|
|
24
|
+
const helperPath = join(repoRoot, ".agents", "scripts", "vault-helper.sh");
|
|
25
|
+
const helperExists = existsSync(helperPath);
|
|
26
|
+
const rawStatus = helperExists ? readVaultCommand(helperPath, ["status"], isGuiVaultStatus) : null;
|
|
27
|
+
const rawSetupState = helperExists ? readVaultCommand(helperPath, ["setup-state"], isGuiVaultSetupState) : null;
|
|
28
|
+
const helperStatus = vaultHelperStatus(helperExists, rawStatus, rawSetupState);
|
|
29
|
+
const status = rawStatus ?? "unknown";
|
|
30
|
+
const setupState = rawSetupState ?? fallbackSetupState(status);
|
|
31
|
+
const unlocked = status === "unlocked";
|
|
32
|
+
const collectionState = vaultCollectionState(status);
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
...statusFixture.vault,
|
|
36
|
+
status,
|
|
37
|
+
setup_state: setupState,
|
|
38
|
+
initialized: INITIALIZED_VAULT_STATUSES.has(status),
|
|
39
|
+
locked: !unlocked,
|
|
40
|
+
unlocked,
|
|
41
|
+
available: helperExists && helperStatus !== "error",
|
|
42
|
+
helper_status: helperStatus,
|
|
43
|
+
readiness: {
|
|
44
|
+
...statusFixture.vault.readiness,
|
|
45
|
+
migration_allowed: unlocked && setupState === "migration-ready",
|
|
46
|
+
setup_required: status === "uninitialized" || setupState === "uninitialized",
|
|
47
|
+
restart_test_required: RESTART_TEST_SETUP_STATES.has(setupState),
|
|
48
|
+
locked_content_hidden: !unlocked,
|
|
49
|
+
},
|
|
50
|
+
collections: statusFixture.vault.collections.map((collection) => ({
|
|
51
|
+
...collection,
|
|
52
|
+
state: collection.state === "planned" ? "planned" : collectionState,
|
|
53
|
+
})),
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function readVaultCommand<T extends string>(
|
|
58
|
+
helperPath: string,
|
|
59
|
+
args: string[],
|
|
60
|
+
isAllowed: (value: string) => value is T,
|
|
61
|
+
): T | null {
|
|
62
|
+
try {
|
|
63
|
+
const output = execFileSync("sh", [helperPath, ...args], {
|
|
64
|
+
encoding: "utf8",
|
|
65
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
66
|
+
timeout: 1_000,
|
|
67
|
+
}).trim();
|
|
68
|
+
const firstLine = output.split(/\r?\n/).find((line) => line.trim().length > 0)?.trim() ?? "";
|
|
69
|
+
return isAllowed(firstLine) ? firstLine : null;
|
|
70
|
+
} catch {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function isGuiVaultStatus(value: string): value is GuiVaultStatus {
|
|
76
|
+
return GUI_VAULT_STATUSES.includes(value as GuiVaultStatus);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function isGuiVaultSetupState(value: string): value is GuiVaultSetupState {
|
|
80
|
+
return GUI_VAULT_SETUP_STATES.includes(value as GuiVaultSetupState);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function vaultHelperStatus(
|
|
84
|
+
helperExists: boolean,
|
|
85
|
+
status: GuiVaultStatus | null,
|
|
86
|
+
setupState: GuiVaultSetupState | null,
|
|
87
|
+
): GuiVaultStatusData["helper_status"] {
|
|
88
|
+
if (!helperExists) {
|
|
89
|
+
return "missing";
|
|
90
|
+
}
|
|
91
|
+
return status === null && setupState === null ? "error" : "available";
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function fallbackSetupState(status: GuiVaultStatus): GuiVaultSetupState {
|
|
95
|
+
return status === "uninitialized" ? "uninitialized" : "unknown";
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function vaultCollectionState(status: GuiVaultStatus): GuiVaultStatusData["collections"][number]["state"] {
|
|
99
|
+
if (status === "unlocked") {
|
|
100
|
+
return "unlocked";
|
|
101
|
+
}
|
|
102
|
+
if (status === "locked" || status === "corrupted") {
|
|
103
|
+
return "locked";
|
|
104
|
+
}
|
|
105
|
+
if (status === "uninitialized") {
|
|
106
|
+
return "not_configured";
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return "unknown";
|
|
110
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export type GuiRouteClassification = "read" | "write" | "destructive";
|
|
2
2
|
|
|
3
|
-
export type GuiOperationId = "setup.status.read" | "capabilities.read" | "filesystem.read";
|
|
3
|
+
export type GuiOperationId = "setup.status.read" | "capabilities.read" | "filesystem.read" | "vault.status.read";
|
|
4
4
|
|
|
5
5
|
export type GuiFileRootId = "agents" | "config" | "localSetup" | "git";
|
|
6
6
|
|
|
@@ -201,6 +201,76 @@ export interface GuiMachineSummary {
|
|
|
201
201
|
public_ip: string | null;
|
|
202
202
|
}
|
|
203
203
|
|
|
204
|
+
export type GuiVaultStatus = "uninitialized" | "locked" | "unlocked" | "corrupted" | "unknown";
|
|
205
|
+
|
|
206
|
+
export type GuiVaultSetupState = "uninitialized" | "test-created" | "restart-required" | "test-verified" | "migration-ready" | "unknown";
|
|
207
|
+
|
|
208
|
+
export type GuiVaultEncryptionState = "not_configured" | "locked" | "unlocked" | "planned" | "unknown";
|
|
209
|
+
|
|
210
|
+
export interface GuiVaultCollectionSummary {
|
|
211
|
+
id: string;
|
|
212
|
+
label: string;
|
|
213
|
+
data_class: string;
|
|
214
|
+
labels: string[];
|
|
215
|
+
surface_ids: string[];
|
|
216
|
+
encrypted: boolean;
|
|
217
|
+
state: GuiVaultEncryptionState;
|
|
218
|
+
preview_policy: "hidden_while_locked" | "metadata_only" | "placeholder_only";
|
|
219
|
+
actions_policy: "disabled_while_locked" | "read_only" | "placeholder_disabled";
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
export interface GuiVaultReadinessSummary {
|
|
223
|
+
migration_allowed: boolean;
|
|
224
|
+
setup_required: boolean;
|
|
225
|
+
restart_test_required: boolean;
|
|
226
|
+
remote_unlock_enabled: boolean;
|
|
227
|
+
provider_routing_enforced: boolean;
|
|
228
|
+
locked_content_hidden: boolean;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
export interface GuiVaultDeviceSummary {
|
|
232
|
+
id_ref: string;
|
|
233
|
+
label: string;
|
|
234
|
+
trust_state: "local" | "pending" | "trusted" | "limited" | "revoked" | "retired" | "planned" | "unknown";
|
|
235
|
+
last_seen: string;
|
|
236
|
+
audit_head_ref: string;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
export interface GuiVaultSyncSummary {
|
|
240
|
+
status: "not_configured" | "planned" | "ready" | "error" | "unknown";
|
|
241
|
+
transport_policy: "encrypted_only_untrusted_transport";
|
|
242
|
+
encrypted_collections: number;
|
|
243
|
+
pending_requests: number;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
export interface GuiVaultAuditSummary {
|
|
247
|
+
status: "not_started" | "planned" | "recording" | "error" | "unknown";
|
|
248
|
+
event_count: number;
|
|
249
|
+
latest_event_ref: string;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
export interface GuiVaultStatusData {
|
|
253
|
+
status: GuiVaultStatus;
|
|
254
|
+
setup_state: GuiVaultSetupState;
|
|
255
|
+
initialized: boolean;
|
|
256
|
+
locked: boolean;
|
|
257
|
+
unlocked: boolean;
|
|
258
|
+
available: boolean;
|
|
259
|
+
helper_status: "available" | "missing" | "error" | "unchecked";
|
|
260
|
+
path_ref: string;
|
|
261
|
+
value_policy: "metadata_only_no_secret_material";
|
|
262
|
+
tooltip: string;
|
|
263
|
+
unlock_hint: string;
|
|
264
|
+
setup_hint: string;
|
|
265
|
+
readiness: GuiVaultReadinessSummary;
|
|
266
|
+
collections: GuiVaultCollectionSummary[];
|
|
267
|
+
devices: GuiVaultDeviceSummary[];
|
|
268
|
+
sync: GuiVaultSyncSummary;
|
|
269
|
+
secure_messages: GuiVaultSyncSummary;
|
|
270
|
+
backups: GuiVaultSyncSummary;
|
|
271
|
+
audit: GuiVaultAuditSummary;
|
|
272
|
+
}
|
|
273
|
+
|
|
204
274
|
export interface GuiStatusData {
|
|
205
275
|
aidevops_version: string;
|
|
206
276
|
update: {
|
|
@@ -231,6 +301,7 @@ export interface GuiStatusData {
|
|
|
231
301
|
oauth_pool: GuiOAuthPoolSummary;
|
|
232
302
|
setup_targets: GuiSetupTargetSummary[];
|
|
233
303
|
ai_apps: GuiAiAppSummary[];
|
|
304
|
+
vault: GuiVaultStatusData;
|
|
234
305
|
capabilities: GuiCapabilitySummary[];
|
|
235
306
|
secrets: GuiSecretReference[];
|
|
236
307
|
placeholders: string[];
|
|
@@ -258,6 +329,17 @@ export const FILE_EXPLORER_ROUTE_MANIFEST: GuiRouteManifest = {
|
|
|
258
329
|
redactions: ["secret_values", "credential_paths", "private_key_material"],
|
|
259
330
|
};
|
|
260
331
|
|
|
332
|
+
export const VAULT_STATUS_ROUTE_MANIFEST: GuiRouteManifest = {
|
|
333
|
+
route: "/api/vault/status",
|
|
334
|
+
method: "GET",
|
|
335
|
+
operation_id: "vault.status.read",
|
|
336
|
+
classification: "read",
|
|
337
|
+
source_surface: "vault",
|
|
338
|
+
adapter: "statusAdapter.readVaultStatus",
|
|
339
|
+
command_pattern: ["aidevops", "vault", "status"],
|
|
340
|
+
redactions: ["secret_values", "credential_paths", "private_key_material", "vault_passphrases", "recovery_material"],
|
|
341
|
+
};
|
|
342
|
+
|
|
261
343
|
export const GUI_FILE_ROOTS: readonly GuiFileRootDefinition[] = [
|
|
262
344
|
{
|
|
263
345
|
id: "agents",
|
|
@@ -320,7 +402,7 @@ export function createEnvelope<TData>(input: {
|
|
|
320
402
|
data: input.data,
|
|
321
403
|
warnings: input.warnings ?? [],
|
|
322
404
|
errors: input.errors ?? [],
|
|
323
|
-
redactions: ["secret_values", "credential_paths", "private_key_material"],
|
|
405
|
+
redactions: ["secret_values", "credential_paths", "private_key_material", "vault_passphrases", "recovery_material"],
|
|
324
406
|
observed_at: input.observed_at ?? new Date().toISOString(),
|
|
325
407
|
};
|
|
326
408
|
}
|
|
@@ -42,6 +42,7 @@ export const statusFixture: GuiStatusData = {
|
|
|
42
42
|
],
|
|
43
43
|
navigation: [
|
|
44
44
|
["overview", "Overview", "Local setup, update, and API health."],
|
|
45
|
+
["vault", "Vault", "Encrypted metadata, setup, sync, and audit."],
|
|
45
46
|
["agents", "Agents", "Read-only file explorer for deployed agent files."],
|
|
46
47
|
["config", "Config", "Read-only file explorer for aidevops config."],
|
|
47
48
|
["git", "Local Repos", "Read-only local git workspace browser."],
|
|
@@ -155,6 +156,106 @@ export const statusFixture: GuiStatusData = {
|
|
|
155
156
|
needs_update: false,
|
|
156
157
|
},
|
|
157
158
|
],
|
|
159
|
+
vault: {
|
|
160
|
+
status: "uninitialized",
|
|
161
|
+
setup_state: "uninitialized",
|
|
162
|
+
initialized: false,
|
|
163
|
+
locked: true,
|
|
164
|
+
unlocked: false,
|
|
165
|
+
available: true,
|
|
166
|
+
helper_status: "unchecked",
|
|
167
|
+
path_ref: "~/.config/aidevops/vault",
|
|
168
|
+
value_policy: "metadata_only_no_secret_material",
|
|
169
|
+
tooltip: "Encrypted by aidevops Vault; contents visible only when unlocked through app or authorised vault commands.",
|
|
170
|
+
unlock_hint: "Run `aidevops vault unlock` in a local terminal and enter the passphrase only into the hidden prompt.",
|
|
171
|
+
setup_hint: "Run `aidevops vault init` locally, verify the restart test, then unlock before protected previews or write actions are available.",
|
|
172
|
+
readiness: {
|
|
173
|
+
migration_allowed: false,
|
|
174
|
+
setup_required: true,
|
|
175
|
+
restart_test_required: false,
|
|
176
|
+
remote_unlock_enabled: false,
|
|
177
|
+
provider_routing_enforced: true,
|
|
178
|
+
locked_content_hidden: true,
|
|
179
|
+
},
|
|
180
|
+
collections: [
|
|
181
|
+
{
|
|
182
|
+
id: "vault-status",
|
|
183
|
+
label: "Vault status and setup",
|
|
184
|
+
data_class: "config metadata",
|
|
185
|
+
labels: ["internal", "confidential"],
|
|
186
|
+
surface_ids: ["vault"],
|
|
187
|
+
encrypted: true,
|
|
188
|
+
state: "not_configured",
|
|
189
|
+
preview_policy: "metadata_only",
|
|
190
|
+
actions_policy: "read_only",
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
id: "agent-guidance",
|
|
194
|
+
label: "Agent guidance and workflows",
|
|
195
|
+
data_class: "session/history",
|
|
196
|
+
labels: ["confidential"],
|
|
197
|
+
surface_ids: ["agents", "localSetup", "config"],
|
|
198
|
+
encrypted: true,
|
|
199
|
+
state: "not_configured",
|
|
200
|
+
preview_policy: "hidden_while_locked",
|
|
201
|
+
actions_policy: "disabled_while_locked",
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
id: "provider-routing",
|
|
205
|
+
label: "Provider routing metadata",
|
|
206
|
+
data_class: "config metadata",
|
|
207
|
+
labels: ["internal", "provider-allowed"],
|
|
208
|
+
surface_ids: ["security", "aiProviders"],
|
|
209
|
+
encrypted: true,
|
|
210
|
+
state: "not_configured",
|
|
211
|
+
preview_policy: "hidden_while_locked",
|
|
212
|
+
actions_policy: "disabled_while_locked",
|
|
213
|
+
},
|
|
214
|
+
{
|
|
215
|
+
id: "fleet-messages",
|
|
216
|
+
label: "Fleet, sync, and secure messages",
|
|
217
|
+
data_class: "mail/messages",
|
|
218
|
+
labels: ["client-confidential"],
|
|
219
|
+
surface_ids: ["devices", "messagingAccounts", "emailAccounts", "inbox", "campaigns", "cases", "projectConfig", "knowledge"],
|
|
220
|
+
encrypted: true,
|
|
221
|
+
state: "planned",
|
|
222
|
+
preview_policy: "hidden_while_locked",
|
|
223
|
+
actions_policy: "disabled_while_locked",
|
|
224
|
+
},
|
|
225
|
+
],
|
|
226
|
+
devices: [
|
|
227
|
+
{
|
|
228
|
+
id_ref: "local-device",
|
|
229
|
+
label: "Local device",
|
|
230
|
+
trust_state: "local",
|
|
231
|
+
last_seen: "current session",
|
|
232
|
+
audit_head_ref: "not started",
|
|
233
|
+
},
|
|
234
|
+
],
|
|
235
|
+
sync: {
|
|
236
|
+
status: "planned",
|
|
237
|
+
transport_policy: "encrypted_only_untrusted_transport",
|
|
238
|
+
encrypted_collections: 0,
|
|
239
|
+
pending_requests: 0,
|
|
240
|
+
},
|
|
241
|
+
secure_messages: {
|
|
242
|
+
status: "planned",
|
|
243
|
+
transport_policy: "encrypted_only_untrusted_transport",
|
|
244
|
+
encrypted_collections: 0,
|
|
245
|
+
pending_requests: 0,
|
|
246
|
+
},
|
|
247
|
+
backups: {
|
|
248
|
+
status: "planned",
|
|
249
|
+
transport_policy: "encrypted_only_untrusted_transport",
|
|
250
|
+
encrypted_collections: 0,
|
|
251
|
+
pending_requests: 0,
|
|
252
|
+
},
|
|
253
|
+
audit: {
|
|
254
|
+
status: "planned",
|
|
255
|
+
event_count: 0,
|
|
256
|
+
latest_event_ref: "not started",
|
|
257
|
+
},
|
|
258
|
+
},
|
|
158
259
|
capabilities: [
|
|
159
260
|
{
|
|
160
261
|
id: "setup-status",
|
|
@@ -252,6 +252,7 @@ function DesktopStatusBar({ status }: { status: GuiStatusData }): ReactElement {
|
|
|
252
252
|
const localRepoCount = status.local_repos.total || status.local_repos.repos.length;
|
|
253
253
|
const remoteRepoCount = status.repos.total || status.repos.repos.length;
|
|
254
254
|
const statusLabel = status.update.restart_required ? "Restart required" : "Ready";
|
|
255
|
+
const vaultLabel = status.vault?.unlocked ? "Vault unlocked" : `Vault ${status.vault?.status ?? "unknown"}`;
|
|
255
256
|
|
|
256
257
|
return (
|
|
257
258
|
<div className="desktop-status-bar" role="status">
|
|
@@ -262,6 +263,7 @@ function DesktopStatusBar({ status }: { status: GuiStatusData }): ReactElement {
|
|
|
262
263
|
<span>{localRepoCount} local repos</span>
|
|
263
264
|
<span>{remoteRepoCount} remote repos</span>
|
|
264
265
|
<span>{status.secrets.length} secrets</span>
|
|
266
|
+
<span>{vaultLabel}</span>
|
|
265
267
|
<span>{oauthAccounts} provider accounts</span>
|
|
266
268
|
<span>{needsUpdate} need update</span>
|
|
267
269
|
</div>
|
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
FiLink,
|
|
23
23
|
FiLink2,
|
|
24
24
|
FiList,
|
|
25
|
+
FiLock,
|
|
25
26
|
FiMail,
|
|
26
27
|
FiMessageSquare,
|
|
27
28
|
FiMonitor,
|
|
@@ -35,6 +36,7 @@ import {
|
|
|
35
36
|
} from "react-icons/fi";
|
|
36
37
|
import type { FontPreference, FontSizePreference, SidebarMode, SurfaceIconName, SurfaceId, SurfaceNavGroup, SurfaceNavItem, ThemePreference } from "./app-model";
|
|
37
38
|
import { DEFAULT_ACCENT_HUE, dashboardNavItem, fontFamilyForPreference, fontOptions, fontSizeOptions, navGroups, sidebarModeForSurface, surfaceRecordCounts, text } from "./app-model";
|
|
39
|
+
import { VaultPadlock, vaultCollectionForSurface } from "./VaultBadges";
|
|
38
40
|
|
|
39
41
|
const surfaceIcons: Record<SurfaceIconName, IconType> = {
|
|
40
42
|
apps: FiBox,
|
|
@@ -53,6 +55,7 @@ const surfaceIcons: Record<SurfaceIconName, IconType> = {
|
|
|
53
55
|
hardDrive: FiHardDrive,
|
|
54
56
|
link: FiLink,
|
|
55
57
|
list: FiList,
|
|
58
|
+
lock: FiLock,
|
|
56
59
|
mail: FiMail,
|
|
57
60
|
message: FiMessageSquare,
|
|
58
61
|
note: FiMessageSquare,
|
|
@@ -138,9 +141,9 @@ export function Sidebar({ activeSurface, accentHue, canGoBack, canGoForward, fon
|
|
|
138
141
|
<nav className="sidebar-content">
|
|
139
142
|
<SidebarModeTabs mode={sidebarMode} setMode={setSidebarMode} />
|
|
140
143
|
<ul className="sidebar-top-link">
|
|
141
|
-
<SidebarItem activeSurface={activeSurface} item={dashboardNavItem} setActiveSurface={setActiveSurface} showCount={false} />
|
|
144
|
+
<SidebarItem activeSurface={activeSurface} item={dashboardNavItem} setActiveSurface={setActiveSurface} showCount={false} status={status} />
|
|
142
145
|
</ul>
|
|
143
|
-
{visibleGroups.map((group) => <SidebarGroup activeSurface={activeSurface} group={group} key={group.label} recordCounts={recordCounts} setActiveSurface={setActiveSurface} showNavCounts={showNavCounts} />)}
|
|
146
|
+
{visibleGroups.map((group) => <SidebarGroup activeSurface={activeSurface} group={group} key={group.label} recordCounts={recordCounts} setActiveSurface={setActiveSurface} showNavCounts={showNavCounts} status={status} />)}
|
|
144
147
|
</nav>
|
|
145
148
|
<SidebarFooter
|
|
146
149
|
accentHue={accentHue}
|
|
@@ -187,32 +190,36 @@ function SidebarModeTabs({ mode, setMode }: {
|
|
|
187
190
|
);
|
|
188
191
|
}
|
|
189
192
|
|
|
190
|
-
function SidebarGroup({ activeSurface, group, recordCounts, setActiveSurface, showNavCounts }: {
|
|
193
|
+
function SidebarGroup({ activeSurface, group, recordCounts, setActiveSurface, showNavCounts, status }: {
|
|
191
194
|
activeSurface: SurfaceId;
|
|
192
195
|
group: SurfaceNavGroup;
|
|
193
196
|
recordCounts: Partial<Record<SurfaceId, number>>;
|
|
194
197
|
setActiveSurface: (surface: SurfaceId) => void;
|
|
195
198
|
showNavCounts: boolean;
|
|
199
|
+
status: GuiStatusData;
|
|
196
200
|
}) {
|
|
197
201
|
return (
|
|
198
202
|
<section className="sidebar-group">
|
|
199
203
|
<h2>{group.label}</h2>
|
|
200
204
|
<ul>
|
|
201
|
-
{group.items.map((item) => <SidebarItem activeSurface={activeSurface} item={item} key={item.id} recordCount={recordCounts[item.id]} setActiveSurface={setActiveSurface} showCount={showNavCounts} />)}
|
|
205
|
+
{group.items.map((item) => <SidebarItem activeSurface={activeSurface} item={item} key={item.id} recordCount={recordCounts[item.id]} setActiveSurface={setActiveSurface} showCount={showNavCounts} status={status} />)}
|
|
202
206
|
</ul>
|
|
203
207
|
</section>
|
|
204
208
|
);
|
|
205
209
|
}
|
|
206
210
|
|
|
207
|
-
function SidebarItem({ activeSurface, item, recordCount, setActiveSurface, showCount }: {
|
|
211
|
+
function SidebarItem({ activeSurface, item, recordCount, setActiveSurface, showCount, status }: {
|
|
208
212
|
activeSurface: SurfaceId;
|
|
209
213
|
item: SurfaceNavItem;
|
|
210
214
|
recordCount?: number;
|
|
211
215
|
setActiveSurface: (surface: SurfaceId) => void;
|
|
212
216
|
showCount: boolean;
|
|
217
|
+
status: GuiStatusData;
|
|
213
218
|
}) {
|
|
214
219
|
const isActive = activeSurface === item.id;
|
|
215
|
-
const
|
|
220
|
+
const vaultCollection = vaultCollectionForSurface(status.vault, item.id);
|
|
221
|
+
const vaultTooltip = vaultCollection ? ` ${text.vaultTooltip}` : "";
|
|
222
|
+
const tooltip = `${item.label}: ${item.description}${vaultTooltip}`;
|
|
216
223
|
const shouldShowCount = showCount && recordCount !== undefined && recordCount > 0;
|
|
217
224
|
|
|
218
225
|
return (
|
|
@@ -232,6 +239,7 @@ function SidebarItem({ activeSurface, item, recordCount, setActiveSurface, showC
|
|
|
232
239
|
{shouldShowCount ? <span className="surface-count">({recordCount})</span> : null}
|
|
233
240
|
</span>
|
|
234
241
|
</span>
|
|
242
|
+
{vaultCollection ? <VaultPadlock collection={vaultCollection} compact vault={status.vault} /> : null}
|
|
235
243
|
{item.badge ? <em>{item.badge}</em> : null}
|
|
236
244
|
</button>
|
|
237
245
|
</li>
|
|
@@ -6,7 +6,8 @@ import type { SurfaceId, SurfaceNavItem } from "./app-model";
|
|
|
6
6
|
import { inventorySurfaceConfigs, text } from "./app-model";
|
|
7
7
|
import { FileExplorerSurface } from "./FileExplorerSurface";
|
|
8
8
|
import { AppsSurface, EditableInventorySurface, InstallationSurface } from "./InventorySurfaces";
|
|
9
|
-
import { AiProvidersSurface, LocalReposSurface, OverviewSurface, PlannedSurface, ProjectsSurface, SecuritySurface } from "./StatusSurfaces";
|
|
9
|
+
import { AiProvidersSurface, LocalReposSurface, LockedVaultGate, OverviewSurface, PlannedSurface, ProjectsSurface, SecuritySurface, VaultSurface } from "./StatusSurfaces";
|
|
10
|
+
import { isVaultSurfaceLocked, vaultCollectionForSurface } from "./VaultBadges";
|
|
10
11
|
|
|
11
12
|
export function Workspace({ activeItem, activeSectionLabel, activeSurface, fileRoot, status }: {
|
|
12
13
|
activeItem: SurfaceNavItem;
|
|
@@ -19,7 +20,7 @@ export function Workspace({ activeItem, activeSectionLabel, activeSurface, fileR
|
|
|
19
20
|
<section className="app-inset" aria-label={text.workspaceLabel}>
|
|
20
21
|
<WorkspaceHeader activeItem={activeItem} activeSectionLabel={activeSectionLabel} version={displayVersion(status)} />
|
|
21
22
|
<div className="workspace-scroll">
|
|
22
|
-
<SurfaceContent activeSurface={activeSurface} fileRoot={fileRoot} status={status} />
|
|
23
|
+
<SurfaceContent activeItem={activeItem} activeSurface={activeSurface} fileRoot={fileRoot} status={status} />
|
|
23
24
|
</div>
|
|
24
25
|
</section>
|
|
25
26
|
);
|
|
@@ -60,14 +61,17 @@ function displayVersion(status: GuiStatusData): string {
|
|
|
60
61
|
return status.aidevops_version;
|
|
61
62
|
}
|
|
62
63
|
|
|
63
|
-
function SurfaceContent({ activeSurface, fileRoot, status }: {
|
|
64
|
+
function SurfaceContent({ activeItem, activeSurface, fileRoot, status }: {
|
|
65
|
+
activeItem: SurfaceNavItem;
|
|
64
66
|
activeSurface: SurfaceId;
|
|
65
67
|
fileRoot: GuiFileRootId | undefined;
|
|
66
68
|
status: GuiStatusData;
|
|
67
69
|
}) {
|
|
68
70
|
const inventoryConfig = inventorySurfaceConfigs[activeSurface];
|
|
71
|
+
const vaultCollection = vaultCollectionForSurface(status.vault, activeSurface);
|
|
69
72
|
const staticSurfaces: Partial<Record<SurfaceId, ReactNode>> = {
|
|
70
73
|
overview: <OverviewSurface status={status} />,
|
|
74
|
+
vault: <VaultSurface status={status} />,
|
|
71
75
|
routines: <PlannedSurface label={text.routines} detail={text.routineDetail} />,
|
|
72
76
|
devices: <PlannedSurface label={text.devices} detail={text.devicesIntro} />,
|
|
73
77
|
vpnsProxies: <PlannedSurface label={text.vpnsProxies} detail={text.vpnsProxiesIntro} />,
|
|
@@ -98,6 +102,10 @@ function SurfaceContent({ activeSurface, fileRoot, status }: {
|
|
|
98
102
|
aiProviders: <AiProvidersSurface status={status} />,
|
|
99
103
|
};
|
|
100
104
|
|
|
105
|
+
if (isVaultSurfaceLocked(status.vault, activeSurface) && vaultCollection) {
|
|
106
|
+
return <LockedVaultGate collection={vaultCollection} label={activeItem.label} vault={status.vault} />;
|
|
107
|
+
}
|
|
108
|
+
|
|
101
109
|
if (activeSurface === "git") {
|
|
102
110
|
return <LocalReposSurface status={status} />;
|
|
103
111
|
}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
/* jshint esversion: 11 */
|
|
2
2
|
import { useState } from "react";
|
|
3
|
-
import type { GuiAiAppSummary, GuiLocalRepoSetupSummary, GuiOAuthProviderSummary, GuiSetupTargetSummary, GuiStatusData } from "../../gui-shared/src";
|
|
3
|
+
import type { GuiAiAppSummary, GuiLocalRepoSetupSummary, GuiOAuthProviderSummary, GuiSetupTargetSummary, GuiStatusData, GuiVaultCollectionSummary, GuiVaultStatusData } from "../../gui-shared/src";
|
|
4
4
|
import { plannedHomes, text } from "./app-model";
|
|
5
5
|
import { FileExplorerSurface } from "./FileExplorerSurface";
|
|
6
6
|
import { PathActions } from "./PathActions";
|
|
7
|
+
import { VaultPadlock } from "./VaultBadges";
|
|
7
8
|
|
|
8
9
|
export function OverviewSurface({ status }: { status: GuiStatusData }) {
|
|
9
10
|
const metrics = [
|
|
@@ -103,6 +104,9 @@ export function SecuritySurface({ status }: { status: GuiStatusData }) {
|
|
|
103
104
|
<h2>{text.security}</h2>
|
|
104
105
|
<p>{text.securityBoundary}</p>
|
|
105
106
|
</div>
|
|
107
|
+
<div className="notice compact-notice" role="note">
|
|
108
|
+
Vault data policy: Provider AI sends approved plaintext context to a third-party provider; Local AI keeps model processing on this device; Hybrid mode must redact or split protected context before any provider call.
|
|
109
|
+
</div>
|
|
106
110
|
<ul className="object-list">
|
|
107
111
|
{status.secrets.map((secret) => (
|
|
108
112
|
<li key={secret.name}>
|
|
@@ -115,6 +119,149 @@ export function SecuritySurface({ status }: { status: GuiStatusData }) {
|
|
|
115
119
|
);
|
|
116
120
|
}
|
|
117
121
|
|
|
122
|
+
export function VaultSurface({ status }: { status: GuiStatusData }) {
|
|
123
|
+
const vault = status.vault;
|
|
124
|
+
const vaultCollection = vault.collections.find((collection) => collection.surface_ids.includes("vault")) ?? vault.collections[0];
|
|
125
|
+
const readiness = [
|
|
126
|
+
{ label: "migration", value: vault.readiness.migration_allowed ? "ready" : "blocked" },
|
|
127
|
+
{ label: "setup", value: vault.readiness.setup_required ? "required" : "done" },
|
|
128
|
+
{ label: "restart test", value: vault.readiness.restart_test_required ? "required" : "verified" },
|
|
129
|
+
{ label: "remote unlock", value: vault.readiness.remote_unlock_enabled ? "enabled" : "disabled" },
|
|
130
|
+
];
|
|
131
|
+
const featureCards = [
|
|
132
|
+
{ label: text.vaultStatus, value: vault.status, detail: "Metadata-only lock state from the local helper." },
|
|
133
|
+
{ label: text.vaultSetup, value: vault.setup_state, detail: vault.setup_hint },
|
|
134
|
+
{ label: text.vaultLockUnlock, value: vault.locked ? "locked" : "unlocked", detail: vault.unlock_hint },
|
|
135
|
+
{ label: text.vaultDevices, value: `${vault.devices.length} device`, detail: "Device trust metadata only; private keys are never exposed." },
|
|
136
|
+
{ label: text.vaultSync, value: vault.sync.status, detail: "Encrypted bundles and signed manifests over untrusted transports." },
|
|
137
|
+
{ label: text.vaultMessages, value: vault.secure_messages.status, detail: "Secure message placeholders keep payloads hidden while locked." },
|
|
138
|
+
{ label: text.vaultBackups, value: vault.backups.status, detail: "Encrypted backups and recovery flows are metadata-only here." },
|
|
139
|
+
{ label: text.vaultAudit, value: vault.audit.status, detail: `${vault.audit.event_count} redacted audit events; ${vault.audit.latest_event_ref}.` },
|
|
140
|
+
];
|
|
141
|
+
|
|
142
|
+
return (
|
|
143
|
+
<section className="surface-page vault-surface" aria-label={text.vault}>
|
|
144
|
+
<div className="hero-panel vault-hero">
|
|
145
|
+
<div className="section-heading split-heading">
|
|
146
|
+
<div>
|
|
147
|
+
<p className="eyebrow">{vault.value_policy}</p>
|
|
148
|
+
<h2>{text.vault}</h2>
|
|
149
|
+
<p>{text.vaultIntro}</p>
|
|
150
|
+
</div>
|
|
151
|
+
{vaultCollection ? <VaultPadlock collection={vaultCollection} vault={vault} /> : null}
|
|
152
|
+
</div>
|
|
153
|
+
<div className="vault-readiness-strip" aria-label="Vault readiness">
|
|
154
|
+
{readiness.map((item) => <span key={item.label}><strong>{item.label}</strong>{item.value}</span>)}
|
|
155
|
+
</div>
|
|
156
|
+
</div>
|
|
157
|
+
{vault.locked ? (
|
|
158
|
+
<div className="notice compact-notice" role="note">
|
|
159
|
+
{text.vaultLockedPreview} {vault.unlock_hint}
|
|
160
|
+
</div>
|
|
161
|
+
) : (
|
|
162
|
+
<div className="notice compact-notice" role="note">
|
|
163
|
+
Vault is unlocked for this local session. Protected actions remain read-only until audited write routes are implemented.
|
|
164
|
+
</div>
|
|
165
|
+
)}
|
|
166
|
+
<div className="vault-card-grid">
|
|
167
|
+
{featureCards.map((card) => <VaultFeatureCard detail={card.detail} key={card.label} label={card.label} value={card.value} />)}
|
|
168
|
+
</div>
|
|
169
|
+
<section className="panel vault-setup-panel" aria-label={text.vaultSetup}>
|
|
170
|
+
<div className="section-heading split-heading">
|
|
171
|
+
<div>
|
|
172
|
+
<p className="eyebrow">{text.vaultSetup}</p>
|
|
173
|
+
<h2>{vault.readiness.setup_required ? "Setup required" : "Setup metadata"}</h2>
|
|
174
|
+
<p>{vault.setup_hint}</p>
|
|
175
|
+
</div>
|
|
176
|
+
<button className="secondary-action vault-cta" disabled title={vault.unlock_hint} type="button">{text.vaultUnlockCta}</button>
|
|
177
|
+
</div>
|
|
178
|
+
<ol className="vault-step-list">
|
|
179
|
+
<li>Initialize locally with the hidden-prompt helper.</li>
|
|
180
|
+
<li>Verify the harmless restart test before migrating real data.</li>
|
|
181
|
+
<li>Keep passphrases, recovery material, and private keys out of chat, arguments, environment variables, logs, issues, and fixtures.</li>
|
|
182
|
+
</ol>
|
|
183
|
+
<code>{vault.unlock_hint}</code>
|
|
184
|
+
</section>
|
|
185
|
+
<section className="panel" aria-label="Vault encrypted collections">
|
|
186
|
+
<div className="section-heading">
|
|
187
|
+
<p className="eyebrow">{text.vaultStatus}</p>
|
|
188
|
+
<h2>Encrypted collections</h2>
|
|
189
|
+
<p>{text.vaultCollectionIntro}</p>
|
|
190
|
+
</div>
|
|
191
|
+
<ul className="object-list vault-collection-list">
|
|
192
|
+
{vault.collections.map((collection) => <VaultCollectionRow collection={collection} key={collection.id} vault={vault} />)}
|
|
193
|
+
</ul>
|
|
194
|
+
</section>
|
|
195
|
+
<section className="panel" aria-label="Vault devices and audit">
|
|
196
|
+
<div className="section-heading split-heading">
|
|
197
|
+
<div>
|
|
198
|
+
<p className="eyebrow">{text.vaultDevices}</p>
|
|
199
|
+
<h2>Devices, sync, messages, backups, and audit</h2>
|
|
200
|
+
<p>These placeholders expose readiness and redacted metadata only. Git, object storage, messaging, SSH, VPNs, and VPS disks remain untrusted transports.</p>
|
|
201
|
+
</div>
|
|
202
|
+
<span className="count-pill">{vault.sync.transport_policy}</span>
|
|
203
|
+
</div>
|
|
204
|
+
<div className="vault-device-grid">
|
|
205
|
+
{vault.devices.map((device) => (
|
|
206
|
+
<article className="vault-device-card" key={device.id_ref}>
|
|
207
|
+
<p className="eyebrow">{device.trust_state}</p>
|
|
208
|
+
<h3>{device.label}</h3>
|
|
209
|
+
<Detail label="device" value={device.id_ref} />
|
|
210
|
+
<Detail label="last seen" value={device.last_seen} />
|
|
211
|
+
<Detail label="audit head" value={device.audit_head_ref} />
|
|
212
|
+
</article>
|
|
213
|
+
))}
|
|
214
|
+
</div>
|
|
215
|
+
</section>
|
|
216
|
+
</section>
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
export function LockedVaultGate({ collection, label, vault }: {
|
|
221
|
+
collection: GuiVaultCollectionSummary;
|
|
222
|
+
label: string;
|
|
223
|
+
vault: GuiVaultStatusData;
|
|
224
|
+
}) {
|
|
225
|
+
return (
|
|
226
|
+
<section className="panel vault-locked-gate" aria-label={`${label} locked by Vault`}>
|
|
227
|
+
<div className="section-heading split-heading">
|
|
228
|
+
<div>
|
|
229
|
+
<p className="eyebrow">{collection.data_class}</p>
|
|
230
|
+
<h2>{label} is locked</h2>
|
|
231
|
+
<p>{text.vaultLockedPreview}</p>
|
|
232
|
+
</div>
|
|
233
|
+
<VaultPadlock collection={collection} vault={vault} />
|
|
234
|
+
</div>
|
|
235
|
+
<div className="notice compact-notice" role="note">
|
|
236
|
+
{text.vaultTooltip} {vault.unlock_hint}
|
|
237
|
+
</div>
|
|
238
|
+
<button className="secondary-action vault-cta" disabled title={vault.unlock_hint} type="button">{text.vaultUnlockCta}</button>
|
|
239
|
+
</section>
|
|
240
|
+
);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function VaultFeatureCard({ detail, label, value }: { detail: string; label: string; value: string }) {
|
|
244
|
+
return (
|
|
245
|
+
<article className="vault-feature-card">
|
|
246
|
+
<span>{label}</span>
|
|
247
|
+
<strong>{value}</strong>
|
|
248
|
+
<small>{detail}</small>
|
|
249
|
+
</article>
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function VaultCollectionRow({ collection, vault }: { collection: GuiVaultCollectionSummary; vault: GuiVaultStatusData }) {
|
|
254
|
+
return (
|
|
255
|
+
<li>
|
|
256
|
+
<strong>{collection.label}</strong>
|
|
257
|
+
<VaultPadlock collection={collection} compact vault={vault} />
|
|
258
|
+
<span>{collection.preview_policy}</span>
|
|
259
|
+
<small>{collection.labels.join(", ")}</small>
|
|
260
|
+
<small>{collection.surface_ids.join(", ")}</small>
|
|
261
|
+
</li>
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
|
|
118
265
|
export function LocalReposSurface({ status }: { status: GuiStatusData }) {
|
|
119
266
|
const [activeTab, setActiveTab] = useState<"setup" | "files">("setup");
|
|
120
267
|
|
|
@@ -309,6 +456,9 @@ export function AiProvidersSurface({ status }: { status: GuiStatusData }) {
|
|
|
309
456
|
</div>
|
|
310
457
|
<span className="count-pill">{status.oauth_pool.value_policy}</span>
|
|
311
458
|
</div>
|
|
459
|
+
<div className="notice compact-notice" role="note">
|
|
460
|
+
Data-leaves-device warning: provider accounts can process prompts outside this machine. Use `data_classification` and `runtime_policy` metadata to require Local AI or explicit provider approval for confidential work.
|
|
461
|
+
</div>
|
|
312
462
|
<div className="provider-grid">
|
|
313
463
|
{status.oauth_pool.providers.map((provider) => <ProviderCard key={provider.provider} provider={provider} />)}
|
|
314
464
|
</div>
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { GuiVaultCollectionSummary, GuiVaultStatusData } from "@aidevops/gui-shared";
|
|
2
|
+
import { FiLock, FiUnlock } from "react-icons/fi";
|
|
3
|
+
import type { SurfaceId } from "./app-model";
|
|
4
|
+
import { text } from "./app-model";
|
|
5
|
+
|
|
6
|
+
export function vaultCollectionForSurface(
|
|
7
|
+
vault: GuiVaultStatusData,
|
|
8
|
+
surface: SurfaceId,
|
|
9
|
+
): GuiVaultCollectionSummary | undefined {
|
|
10
|
+
return vault.collections.find((collection) => collection.surface_ids.includes(surface));
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function isVaultSurfaceLocked(vault: GuiVaultStatusData, surface: SurfaceId): boolean {
|
|
14
|
+
const collection = vaultCollectionForSurface(vault, surface);
|
|
15
|
+
return surface !== "vault" && collection?.encrypted === true && !vault.unlocked;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function VaultPadlock({ collection, compact = false, vault }: {
|
|
19
|
+
collection: GuiVaultCollectionSummary;
|
|
20
|
+
compact?: boolean;
|
|
21
|
+
vault: GuiVaultStatusData;
|
|
22
|
+
}) {
|
|
23
|
+
const locked = collection.state !== "unlocked" || !vault.unlocked;
|
|
24
|
+
const stateLabel = locked ? "Locked" : "Unlocked";
|
|
25
|
+
const Icon = locked ? FiLock : FiUnlock;
|
|
26
|
+
const tooltip = `${stateLabel}: ${text.vaultTooltip}`;
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<span
|
|
30
|
+
aria-label={`${stateLabel} by aidevops Vault: ${collection.label}`}
|
|
31
|
+
className={compact ? "vault-padlock compact" : "vault-padlock"}
|
|
32
|
+
data-vault-state={locked ? "locked" : "unlocked"}
|
|
33
|
+
title={tooltip}
|
|
34
|
+
>
|
|
35
|
+
<Icon aria-hidden="true" focusable="false" />
|
|
36
|
+
<span>{compact ? stateLabel : `${stateLabel} by Vault`}</span>
|
|
37
|
+
</span>
|
|
38
|
+
);
|
|
39
|
+
}
|
|
@@ -32,6 +32,7 @@ export const surfaceIconNames = {
|
|
|
32
32
|
hardDrive: true,
|
|
33
33
|
link: true,
|
|
34
34
|
list: true,
|
|
35
|
+
lock: true,
|
|
35
36
|
mail: true,
|
|
36
37
|
message: true,
|
|
37
38
|
note: true,
|
|
@@ -45,6 +46,7 @@ export const surfaceIconNames = {
|
|
|
45
46
|
export type SurfaceIconName = keyof typeof surfaceIconNames;
|
|
46
47
|
export const surfaceIds = [
|
|
47
48
|
"overview",
|
|
49
|
+
"vault",
|
|
48
50
|
"agents",
|
|
49
51
|
"config",
|
|
50
52
|
"localSetup",
|
|
@@ -180,7 +182,7 @@ export const text = {
|
|
|
180
182
|
aiApps: "AI Apps",
|
|
181
183
|
aiAppsIntro: "Local AI app, CLI, config, and aidevops prompt targets. Versions are read-only and compare installed aidevops files with the current checkout.",
|
|
182
184
|
aiProviders: "AI Providers",
|
|
183
|
-
aiProvidersIntro: "OAuth pool account metadata by provider. Tokens stay hidden;
|
|
185
|
+
aiProvidersIntro: "OAuth pool account metadata by provider. Tokens stay hidden; Provider AI means task data leaves this device unless Vault policy routes it to Local AI or Hybrid mode.",
|
|
184
186
|
madeForCreators: "Made for creators",
|
|
185
187
|
maintenance: "Maintenance",
|
|
186
188
|
management: "Management",
|
|
@@ -218,7 +220,7 @@ export const text = {
|
|
|
218
220
|
searchPlaceholder: "Search setup, files, providers",
|
|
219
221
|
secrets: "Secrets",
|
|
220
222
|
security: "Secrets",
|
|
221
|
-
securityBoundary: "No write routes, no shell bridge, no hosted app control, no pairing, no secret values.",
|
|
223
|
+
securityBoundary: "No write routes, no shell bridge, no hosted app control, no pairing, no secret values. Vault policy distinguishes Provider AI, Local AI, and Hybrid modes before protected data leaves the device.",
|
|
222
224
|
servers: "Servers",
|
|
223
225
|
serversIntro: "Server inventory by provider, operating system, orchestrator, and installed apps.",
|
|
224
226
|
setup: "Setup",
|
|
@@ -238,6 +240,21 @@ export const text = {
|
|
|
238
240
|
fontSize: "Font size",
|
|
239
241
|
truncated: "Preview truncated for safety.",
|
|
240
242
|
update: "Update",
|
|
243
|
+
vault: "Vault",
|
|
244
|
+
vaultAudit: "Audit Logs",
|
|
245
|
+
vaultBackups: "Backups & Recovery",
|
|
246
|
+
vaultCollectionIntro: "Encrypted collections are metadata-only while locked. Protected previews and write actions stay disabled until a local unlock succeeds.",
|
|
247
|
+
vaultDevices: "Devices",
|
|
248
|
+
vaultIntro: "Local encrypted metadata, lock/unlock readiness, device trust, encrypted sync, secure messages, backups, and audit placeholders.",
|
|
249
|
+
vaultLockedPreview: "Vault locked: protected previews are hidden and write actions are disabled.",
|
|
250
|
+
vaultLockUnlock: "Lock / Unlock",
|
|
251
|
+
vaultMessages: "Secure Messages",
|
|
252
|
+
vaultSetup: "First-use Setup",
|
|
253
|
+
vaultStatus: "Status",
|
|
254
|
+
vaultSync: "Sync",
|
|
255
|
+
vaultTooltip: "Encrypted by aidevops Vault; contents visible only when unlocked through app or authorised vault commands.",
|
|
256
|
+
vaultUnlockCta: "Unlock Vault",
|
|
257
|
+
vaultUnlockHint: "Run `aidevops vault unlock` in a local terminal and enter the passphrase only into the hidden prompt.",
|
|
241
258
|
website: "Website",
|
|
242
259
|
websites: "Websites",
|
|
243
260
|
websitesIntro: "Websites, landing pages, publishing homes, and ownership status are planned.",
|
|
@@ -286,6 +303,7 @@ export const navGroups: SurfaceNavGroup[] = [
|
|
|
286
303
|
label: text.operations,
|
|
287
304
|
mode: "devops",
|
|
288
305
|
items: [
|
|
306
|
+
{ id: "vault", label: text.vault, description: "Encrypted metadata, setup, sync, and audit", icon: "lock" },
|
|
289
307
|
{ id: "agents", label: "Agents", description: "~/.aidevops/agents explorer", icon: "document" },
|
|
290
308
|
{ id: "config", label: text.config, description: "~/.config/aidevops explorer", icon: "settings" },
|
|
291
309
|
{ id: "localSetup", label: text.localSetup, description: "~/.aidevops explorer", icon: "terminal" },
|
|
@@ -392,6 +410,7 @@ export const installationRows = [
|
|
|
392
410
|
|
|
393
411
|
export const plannedHomes = [
|
|
394
412
|
{ area: "Setup/status", home: "Dashboard + Local Setup", phase: "P6" },
|
|
413
|
+
{ area: "Vault", home: "Vault status, setup, devices, sync, messages, backups, audit", phase: "P6/Vault" },
|
|
395
414
|
{ area: "Local and remote repos", home: "Local Repos + Remote Repos", phase: "P7" },
|
|
396
415
|
{ area: "Infrastructure inventory", home: "Domains, Registrars, Hosts, Servers", phase: "P8" },
|
|
397
416
|
{ area: "Provider catalog", home: "Registrars, Hosts, Apps", phase: "P9" },
|
|
@@ -536,6 +555,7 @@ export function surfaceRecordCounts(status: GuiStatusData): SurfaceRecordCounts
|
|
|
536
555
|
registrars: populatedInventoryRowCount("registrars"),
|
|
537
556
|
security: status.secrets.length,
|
|
538
557
|
servers: populatedInventoryRowCount("servers"),
|
|
558
|
+
vault: status.vault.collections.length,
|
|
539
559
|
};
|
|
540
560
|
}
|
|
541
561
|
|
|
@@ -27,6 +27,9 @@ export function renderDashboardHtml(status: GuiResponseEnvelope<GuiStatusData>):
|
|
|
27
27
|
const aiApps = status.data.ai_apps
|
|
28
28
|
.map((app) => `<li>${escapeHtml(app.name)}: ${escapeHtml(app.status)} ${escapeHtml(app.aidevops_version)} → ${escapeHtml(app.latest_version)}</li>`)
|
|
29
29
|
.join("");
|
|
30
|
+
const vaultCollections = status.data.vault.collections
|
|
31
|
+
.map((collection) => `<li>${escapeHtml(collection.label)}: ${escapeHtml(collection.state)} ${escapeHtml(collection.preview_policy)}</li>`)
|
|
32
|
+
.join("");
|
|
30
33
|
const settings = status.data.settings.keys
|
|
31
34
|
.map((key) => `<li>${escapeHtml(key)}</li>`)
|
|
32
35
|
.join("");
|
|
@@ -35,7 +38,7 @@ export function renderDashboardHtml(status: GuiResponseEnvelope<GuiStatusData>):
|
|
|
35
38
|
.join("");
|
|
36
39
|
const navSections = [
|
|
37
40
|
{ heading: "Development", items: ["Local Repos", "Remote Repos", "Secrets", "AI Providers"] },
|
|
38
|
-
{ heading: "Operations", items: ["Dashboard", "Agents file explorer", "Config", "Local Setup", "Routines"] },
|
|
41
|
+
{ heading: "Operations", items: ["Dashboard", "Vault", "Agents file explorer", "Config", "Local Setup", "Routines"] },
|
|
39
42
|
{ heading: "Infrastructure", items: ["Devices", "VPNs & Proxies", "Apps", "Installation", "Registrars", "Hosts", "Servers"] },
|
|
40
43
|
{ heading: "Identities", items: ["Brands", "Domains", "Personas"] },
|
|
41
44
|
{ heading: "Sites", items: ["Websites", "Forums", "Social Media", "Marketplaces"] },
|
|
@@ -53,11 +56,13 @@ export function renderDashboardHtml(status: GuiResponseEnvelope<GuiStatusData>):
|
|
|
53
56
|
`<p>AI-assisted development workflows, code quality, and deployment automation.</p>`,
|
|
54
57
|
`<p>Theme follows system preferences with light and dark overrides. Sidebar modes: DevOps and Comms. Appearance controls can be hidden or shown, and include editable Hue, icon Reset, Show borders toggle, Show counts toggle, Font size choices xs, s, m, lg, xl, and Font options: IBM Plex Mono, IBM Plex Sans, IBM Plex Serif, Inter, Menlo (default), Playpen Sans, Poppins, Source Sans, Source Serif, Tilt Neon, Ubuntu Mono. A desktop status bar shows local readiness, repo totals, secret reference count, provider accounts, and update state.</p>`,
|
|
55
58
|
`<p>Secret references stay read-only and redacted.</p>`,
|
|
59
|
+
`<p>Vault surfaces show padlock indicators. Encrypted by aidevops Vault; contents visible only when unlocked through app or authorised vault commands.</p>`,
|
|
56
60
|
`<p>${escapeHtml(status.data.update.message)}</p>`,
|
|
57
61
|
renderedNavSections,
|
|
58
62
|
`<h2>Path health</h2><ul>${paths}</ul>`,
|
|
59
63
|
`<h2>Installed aidevops targets</h2><ul>${setupTargets}</ul>`,
|
|
60
64
|
`<h2>AI Apps</h2><ul>${aiApps}</ul>`,
|
|
65
|
+
`<h2>Vault</h2><p>${escapeHtml(status.data.vault.status)} / ${escapeHtml(status.data.vault.setup_state)}</p><ul>${vaultCollections}</ul>`,
|
|
61
66
|
`<h2>Local Repo Setup</h2><ul>${localRepos}</ul>`,
|
|
62
67
|
`<h2>Remote Repos</h2><ul>${repos}</ul>`,
|
|
63
68
|
`<h2>AI Provider Pools</h2><ul>${aiProviders}</ul>`,
|
|
@@ -85,6 +85,17 @@ function normalizeStatusEnvelope(envelope: GuiResponseEnvelope<Partial<GuiStatus
|
|
|
85
85
|
oauth_pool: { ...statusFixture.oauth_pool, ...data.oauth_pool, providers: data.oauth_pool?.providers ?? statusFixture.oauth_pool.providers },
|
|
86
86
|
setup_targets: data.setup_targets ?? statusFixture.setup_targets,
|
|
87
87
|
ai_apps: data.ai_apps ?? statusFixture.ai_apps,
|
|
88
|
+
vault: {
|
|
89
|
+
...statusFixture.vault,
|
|
90
|
+
...data.vault,
|
|
91
|
+
readiness: { ...statusFixture.vault.readiness, ...data.vault?.readiness },
|
|
92
|
+
collections: data.vault?.collections ?? statusFixture.vault.collections,
|
|
93
|
+
devices: data.vault?.devices ?? statusFixture.vault.devices,
|
|
94
|
+
sync: { ...statusFixture.vault.sync, ...data.vault?.sync },
|
|
95
|
+
secure_messages: { ...statusFixture.vault.secure_messages, ...data.vault?.secure_messages },
|
|
96
|
+
backups: { ...statusFixture.vault.backups, ...data.vault?.backups },
|
|
97
|
+
audit: { ...statusFixture.vault.audit, ...data.vault?.audit },
|
|
98
|
+
},
|
|
88
99
|
capabilities: data.capabilities ?? statusFixture.capabilities,
|
|
89
100
|
secrets: data.secrets ?? statusFixture.secrets,
|
|
90
101
|
placeholders: data.placeholders ?? statusFixture.placeholders,
|
|
@@ -430,6 +430,39 @@ code {
|
|
|
430
430
|
font-weight: 700;
|
|
431
431
|
}
|
|
432
432
|
|
|
433
|
+
.vault-padlock {
|
|
434
|
+
align-items: center;
|
|
435
|
+
background: color-mix(in srgb, #d97706 13%, transparent);
|
|
436
|
+
border: 1px solid color-mix(in srgb, #d97706 38%, transparent);
|
|
437
|
+
border-radius: 999px;
|
|
438
|
+
color: #b45309;
|
|
439
|
+
display: inline-flex;
|
|
440
|
+
flex: 0 0 auto;
|
|
441
|
+
font-size: 0.74rem;
|
|
442
|
+
font-weight: 900;
|
|
443
|
+
gap: 4px;
|
|
444
|
+
padding: 5px 9px;
|
|
445
|
+
text-transform: uppercase;
|
|
446
|
+
white-space: nowrap;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
.vault-padlock svg {
|
|
450
|
+
height: 14px;
|
|
451
|
+
stroke-width: 2.6;
|
|
452
|
+
width: 14px;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
.vault-padlock.compact {
|
|
456
|
+
font-size: 0.66rem;
|
|
457
|
+
padding: 3px 6px;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
.vault-padlock[data-vault-state="unlocked"] {
|
|
461
|
+
background: color-mix(in srgb, var(--accent) 14%, transparent);
|
|
462
|
+
border-color: color-mix(in srgb, var(--accent) 38%, transparent);
|
|
463
|
+
color: var(--accent);
|
|
464
|
+
}
|
|
465
|
+
|
|
433
466
|
.sidebar-group h2 {
|
|
434
467
|
font-size: max(12.5px, 0.84em);
|
|
435
468
|
}
|
|
@@ -755,6 +788,32 @@ code {
|
|
|
755
788
|
padding: clamp(24px, 5vw, 42px);
|
|
756
789
|
}
|
|
757
790
|
|
|
791
|
+
.vault-hero {
|
|
792
|
+
display: grid;
|
|
793
|
+
gap: 16px;
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
.vault-readiness-strip {
|
|
797
|
+
display: flex;
|
|
798
|
+
flex-wrap: wrap;
|
|
799
|
+
gap: 8px;
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
.vault-readiness-strip span {
|
|
803
|
+
background: var(--surface-elevated);
|
|
804
|
+
border: 1px solid var(--border);
|
|
805
|
+
border-radius: 999px;
|
|
806
|
+
color: var(--text-secondary);
|
|
807
|
+
display: inline-flex;
|
|
808
|
+
gap: 6px;
|
|
809
|
+
padding: 7px 10px;
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
.vault-readiness-strip strong {
|
|
813
|
+
color: var(--accent);
|
|
814
|
+
text-transform: uppercase;
|
|
815
|
+
}
|
|
816
|
+
|
|
758
817
|
.compact-hero h2 {
|
|
759
818
|
font-size: clamp(3rem, 10vw, 5rem);
|
|
760
819
|
letter-spacing: -0.08em;
|
|
@@ -1141,7 +1200,9 @@ code {
|
|
|
1141
1200
|
.repo-card-grid,
|
|
1142
1201
|
.provider-grid,
|
|
1143
1202
|
.setup-card-grid,
|
|
1144
|
-
.ai-app-grid
|
|
1203
|
+
.ai-app-grid,
|
|
1204
|
+
.vault-card-grid,
|
|
1205
|
+
.vault-device-grid {
|
|
1145
1206
|
display: grid;
|
|
1146
1207
|
gap: 14px;
|
|
1147
1208
|
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
|
@@ -1150,7 +1211,9 @@ code {
|
|
|
1150
1211
|
.repo-setup-card,
|
|
1151
1212
|
.provider-card,
|
|
1152
1213
|
.setup-target-card,
|
|
1153
|
-
.ai-app-card
|
|
1214
|
+
.ai-app-card,
|
|
1215
|
+
.vault-feature-card,
|
|
1216
|
+
.vault-device-card {
|
|
1154
1217
|
background: var(--surface-muted);
|
|
1155
1218
|
border: 1px solid var(--border);
|
|
1156
1219
|
border-radius: 18px;
|
|
@@ -1175,11 +1238,48 @@ code {
|
|
|
1175
1238
|
.repo-setup-card h3,
|
|
1176
1239
|
.provider-card h3,
|
|
1177
1240
|
.setup-target-card h3,
|
|
1178
|
-
.ai-app-card h3
|
|
1241
|
+
.ai-app-card h3,
|
|
1242
|
+
.vault-device-card h3 {
|
|
1179
1243
|
font-size: 1rem;
|
|
1180
1244
|
margin: 0 0 5px;
|
|
1181
1245
|
}
|
|
1182
1246
|
|
|
1247
|
+
.vault-feature-card span {
|
|
1248
|
+
color: var(--accent);
|
|
1249
|
+
font-size: 0.76rem;
|
|
1250
|
+
font-weight: 900;
|
|
1251
|
+
text-transform: uppercase;
|
|
1252
|
+
}
|
|
1253
|
+
|
|
1254
|
+
.vault-feature-card strong {
|
|
1255
|
+
display: block;
|
|
1256
|
+
font-size: 1.35rem;
|
|
1257
|
+
letter-spacing: -0.04em;
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
.vault-feature-card small {
|
|
1261
|
+
color: var(--text-muted);
|
|
1262
|
+
}
|
|
1263
|
+
|
|
1264
|
+
.vault-step-list {
|
|
1265
|
+
color: var(--text-secondary);
|
|
1266
|
+
margin: 0;
|
|
1267
|
+
padding-left: 1.2rem;
|
|
1268
|
+
}
|
|
1269
|
+
|
|
1270
|
+
.vault-setup-panel code {
|
|
1271
|
+
display: inline-block;
|
|
1272
|
+
margin-top: 10px;
|
|
1273
|
+
}
|
|
1274
|
+
|
|
1275
|
+
.vault-cta {
|
|
1276
|
+
justify-self: start;
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1279
|
+
.vault-collection-list li {
|
|
1280
|
+
align-items: center;
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1183
1283
|
.repo-setup-card code,
|
|
1184
1284
|
.repo-remotes code,
|
|
1185
1285
|
.setup-path-row code {
|
package/setup.sh
CHANGED
|
@@ -12,7 +12,7 @@ shopt -s inherit_errexit 2>/dev/null || true
|
|
|
12
12
|
# AI Assistant Server Access Framework Setup Script
|
|
13
13
|
# Helps developers set up the framework for their infrastructure
|
|
14
14
|
#
|
|
15
|
-
# Version: 3.22.
|
|
15
|
+
# Version: 3.22.5
|
|
16
16
|
#
|
|
17
17
|
# Quick Install:
|
|
18
18
|
# npm install -g aidevops && aidevops update (recommended)
|