vgxness 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (121) hide show
  1. package/LICENSE +9 -0
  2. package/README.md +110 -0
  3. package/dist/agents/agent-activation-service.js +144 -0
  4. package/dist/agents/agent-registry-service.js +46 -0
  5. package/dist/agents/agent-resolver.js +249 -0
  6. package/dist/agents/agent-seed-service.js +146 -0
  7. package/dist/agents/manager-profile-overlay-service.js +34 -0
  8. package/dist/agents/profile-model-routing.js +26 -0
  9. package/dist/agents/renderers/claude-renderer.js +98 -0
  10. package/dist/agents/renderers/index.js +16 -0
  11. package/dist/agents/renderers/json-renderer.js +87 -0
  12. package/dist/agents/renderers/opencode-renderer.js +100 -0
  13. package/dist/agents/renderers/provider-adapter.js +6 -0
  14. package/dist/agents/repositories/agents.js +185 -0
  15. package/dist/agents/repositories/manager-profile-overlays.js +81 -0
  16. package/dist/agents/schema.js +1 -0
  17. package/dist/cli/dashboard-operational-read-models.js +153 -0
  18. package/dist/cli/dashboard-renderer.js +109 -0
  19. package/dist/cli/dashboard-screen-renderers.js +332 -0
  20. package/dist/cli/dashboard-tui-read-model.js +71 -0
  21. package/dist/cli/dashboard-tui-state.js +218 -0
  22. package/dist/cli/dispatcher.js +2880 -0
  23. package/dist/cli/index.js +27 -0
  24. package/dist/cli/interactive-dashboard.js +29 -0
  25. package/dist/cli/mcp-start-path.js +21 -0
  26. package/dist/cli/setup-status-renderer.js +29 -0
  27. package/dist/cli/setup-wizard-read-model.js +56 -0
  28. package/dist/cli/setup-wizard-renderer.js +148 -0
  29. package/dist/cli/setup-wizard-state.js +82 -0
  30. package/dist/cli/tui-render-helpers.js +192 -0
  31. package/dist/export/redaction.js +71 -0
  32. package/dist/harness/tools/agents.js +245 -0
  33. package/dist/harness/tools/memory.js +29 -0
  34. package/dist/mcp/client-install-opencode-contract.js +227 -0
  35. package/dist/mcp/client-install-opencode.js +194 -0
  36. package/dist/mcp/client-setup-preview.js +38 -0
  37. package/dist/mcp/control-plane.js +175 -0
  38. package/dist/mcp/doctor.js +193 -0
  39. package/dist/mcp/index.js +10 -0
  40. package/dist/mcp/opencode-default-agent-config.js +156 -0
  41. package/dist/mcp/opencode-visibility.js +102 -0
  42. package/dist/mcp/schema.js +234 -0
  43. package/dist/mcp/stdio-server.js +56 -0
  44. package/dist/mcp/validation.js +761 -0
  45. package/dist/memory/import/dry-run-planner.js +58 -0
  46. package/dist/memory/import/index.js +3 -0
  47. package/dist/memory/import/observation-writer.js +220 -0
  48. package/dist/memory/import/package.js +178 -0
  49. package/dist/memory/memory-service.js +126 -0
  50. package/dist/memory/repositories/artifacts.js +41 -0
  51. package/dist/memory/repositories/observations.js +133 -0
  52. package/dist/memory/repositories/sessions.js +105 -0
  53. package/dist/memory/repositories/traces.js +58 -0
  54. package/dist/memory/schema.js +1 -0
  55. package/dist/memory/search.js +11 -0
  56. package/dist/memory/sqlite/database.js +97 -0
  57. package/dist/memory/sqlite/migrations/001_initial.sql +128 -0
  58. package/dist/memory/sqlite/migrations/002_observation_revisions.sql +14 -0
  59. package/dist/memory/sqlite/migrations/003_agent_registry.sql +26 -0
  60. package/dist/memory/sqlite/migrations/004_run_runtime.sql +62 -0
  61. package/dist/memory/sqlite/migrations/005_run_approvals.sql +20 -0
  62. package/dist/memory/sqlite/migrations/006_run_operation_attempts.sql +32 -0
  63. package/dist/memory/sqlite/migrations/007_abandoned_operation_attempts.sql +46 -0
  64. package/dist/memory/sqlite/migrations/008_run_execution_plan_events.sql +105 -0
  65. package/dist/memory/sqlite/migrations/009_multiple_operation_attempts.sql +73 -0
  66. package/dist/memory/sqlite/migrations/010_skill_registry.sql +66 -0
  67. package/dist/memory/sqlite/migrations/011_skill_usage_resolution_outcomes.sql +21 -0
  68. package/dist/memory/sqlite/migrations/012_skill_improvement_proposals.sql +37 -0
  69. package/dist/memory/sqlite/migrations/013_skill_evaluation_scenarios.sql +43 -0
  70. package/dist/memory/sqlite/migrations/014_manager_profile_overlays.sql +14 -0
  71. package/dist/memory/storage-paths.js +72 -0
  72. package/dist/orchestrator/natural-language-planner.js +191 -0
  73. package/dist/orchestrator/schema.js +1 -0
  74. package/dist/permissions/index.js +2 -0
  75. package/dist/permissions/policy-evaluator.js +109 -0
  76. package/dist/permissions/schema.js +1 -0
  77. package/dist/providers/opencode/injection-preview.js +134 -0
  78. package/dist/providers/opencode/manager-payload.js +129 -0
  79. package/dist/runs/execution-planning.js +117 -0
  80. package/dist/runs/operation-execution.js +1 -0
  81. package/dist/runs/operation-retry.js +124 -0
  82. package/dist/runs/repositories/runs.js +611 -0
  83. package/dist/runs/run-insights.js +145 -0
  84. package/dist/runs/run-service.js +713 -0
  85. package/dist/runs/run-snapshot-export-service.js +31 -0
  86. package/dist/runs/sandbox-process-execution.js +218 -0
  87. package/dist/runs/sandbox-worktree-planning.js +59 -0
  88. package/dist/runs/schema.js +1 -0
  89. package/dist/sdd/artifact-portability-service.js +118 -0
  90. package/dist/sdd/schema.js +17 -0
  91. package/dist/sdd/sdd-workflow-service.js +217 -0
  92. package/dist/setup/backup-rollback-service.js +76 -0
  93. package/dist/setup/index.js +3 -0
  94. package/dist/setup/providers/antigravity-setup-adapter.js +18 -0
  95. package/dist/setup/providers/claude-setup-adapter.js +30 -0
  96. package/dist/setup/providers/custom-setup-adapter.js +18 -0
  97. package/dist/setup/providers/index.js +6 -0
  98. package/dist/setup/providers/opencode-setup-adapter.js +104 -0
  99. package/dist/setup/providers/provider-setup-adapter.js +15 -0
  100. package/dist/setup/providers/provider-setup-registry.js +11 -0
  101. package/dist/setup/schema.js +1 -0
  102. package/dist/setup/setup-defaults.js +11 -0
  103. package/dist/setup/setup-lifecycle-service.js +175 -0
  104. package/dist/setup/setup-plan.js +105 -0
  105. package/dist/skills/repositories/skill-evaluation-scenarios.js +289 -0
  106. package/dist/skills/repositories/skill-improvement-proposals.js +288 -0
  107. package/dist/skills/repositories/skills.js +430 -0
  108. package/dist/skills/schema.js +1 -0
  109. package/dist/skills/skill-payload.js +94 -0
  110. package/dist/skills/skill-registry-service.js +92 -0
  111. package/dist/skills/skill-resolver.js +191 -0
  112. package/dist/workflows/command-allowlist-adapter.js +70 -0
  113. package/dist/workflows/schema.js +4 -0
  114. package/dist/workflows/workflow-executor.js +345 -0
  115. package/dist/workflows/workflow-registry.js +66 -0
  116. package/docs/architecture.md +698 -0
  117. package/docs/cli.md +741 -0
  118. package/docs/funcionamiento-del-sistema.md +868 -0
  119. package/docs/harness-gap-analysis.md +229 -0
  120. package/docs/prd.md +372 -0
  121. package/package.json +57 -0
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env node
2
+ import { dispatchCliAsync } from './dispatcher.js';
3
+ import { resolveMcpStartDatabasePath } from './mcp-start-path.js';
4
+ import { startVgxMcpStdioServer } from '../mcp/stdio-server.js';
5
+ void main();
6
+ async function main() {
7
+ const argv = process.argv.slice(2);
8
+ if (isMcpStartCommand(argv)) {
9
+ const databasePath = resolveMcpStartDatabasePath(argv, process.cwd(), process.env, process.platform);
10
+ if (!databasePath.ok) {
11
+ process.stderr.write(`${databasePath.error.message}\n`);
12
+ process.exitCode = 1;
13
+ return;
14
+ }
15
+ await startVgxMcpStdioServer({ databasePath: databasePath.value });
16
+ return;
17
+ }
18
+ const result = await dispatchCliAsync(argv, { cwd: process.cwd(), env: process.env, stdin: process.stdin, stdout: process.stdout });
19
+ if (result.stdout)
20
+ process.stdout.write(result.stdout);
21
+ if (result.stderr)
22
+ process.stderr.write(result.stderr);
23
+ process.exitCode = result.exitCode;
24
+ }
25
+ function isMcpStartCommand(argv) {
26
+ return argv[0] === 'mcp' && argv[1] === 'start';
27
+ }
@@ -0,0 +1,29 @@
1
+ import { renderDashboardFrame } from './dashboard-screen-renderers.js';
2
+ import { createDashboardState as createState, dashboardKeyFromInput, reduceDashboardKey, refreshDashboardState } from './dashboard-tui-state.js';
3
+ import { sanitizeDashboardError } from './dashboard-tui-read-model.js';
4
+ export function createDashboardState(input) {
5
+ return createState(input);
6
+ }
7
+ export async function loadInitialDashboardState(project, loader) {
8
+ const loaded = await loader();
9
+ if (!loaded.ok)
10
+ return createDashboardState({ ...(project === undefined ? {} : { project }), runs: [], activeScreen: project === undefined ? 'setup' : 'runs', error: sanitizeDashboardError(loaded.error.message) });
11
+ return createDashboardState({ ...(project === undefined ? {} : { project }), ...loaded.value });
12
+ }
13
+ export { dashboardKeyFromInput, reduceDashboardKey };
14
+ export async function refreshDashboard(state, loader) {
15
+ const loaded = await loader();
16
+ if (!loaded.ok)
17
+ return { ...state, error: sanitizeDashboardError(loaded.error.message) };
18
+ return refreshDashboardState(state, loaded.value);
19
+ }
20
+ export function renderDashboard(state, options = {}) {
21
+ return renderDashboardFrame(state, options);
22
+ }
23
+ export function resolveDashboardRenderStyle(input) {
24
+ if (input.isTTY !== true)
25
+ return 'plain';
26
+ if (input.env?.NO_COLOR !== undefined || input.env?.CI !== undefined)
27
+ return 'plain';
28
+ return 'ansi';
29
+ }
@@ -0,0 +1,21 @@
1
+ import { resolveMemoryDatabasePath } from '../memory/storage-paths.js';
2
+ export function resolveMcpStartDatabasePath(argv, cwd, env, platform = process.platform) {
3
+ const explicit = readFlagValue(argv, 'db');
4
+ const resolved = resolveMemoryDatabasePath({ cwd, env, explicitPath: explicit, platform });
5
+ return resolved.ok ? { ok: true, value: resolved.value.path } : resolved;
6
+ }
7
+ function readFlagValue(argv, name) {
8
+ const prefix = `--${name}=`;
9
+ for (let index = 0; index < argv.length; index += 1) {
10
+ const current = argv[index];
11
+ if (current === undefined)
12
+ continue;
13
+ if (current.startsWith(prefix))
14
+ return current.slice(prefix.length);
15
+ if (current === `--${name}`) {
16
+ const next = argv[index + 1];
17
+ return next !== undefined && !next.startsWith('--') ? next : undefined;
18
+ }
19
+ }
20
+ return undefined;
21
+ }
@@ -0,0 +1,29 @@
1
+ export function renderSetupStatus(status) {
2
+ return [
3
+ 'Setup Status',
4
+ `- Environment: ${status.environment.status} — ${status.environment.summary}`,
5
+ `- Project: ${status.projectState.status} — ${status.projectState.summary}`,
6
+ `- Store: ${status.store.status}${status.store.path === '' ? '' : ` — ${status.store.path}`}${status.store.blocker === undefined ? '' : ` (${status.store.blocker})`}`,
7
+ `- Providers: ${status.providers.map(renderProvider).join('; ')}`,
8
+ `- Agents: ${status.agents.deferred ? 'deferred' : status.agents.status} — ${renderDefaults(status)}`,
9
+ `- Verification: ${status.verification.status} — ${status.verification.summary}`,
10
+ `- Next setup action: ${status.nextAction.command} — ${status.nextAction.reason}`,
11
+ `- Provider writes: ${status.safety.writesProviderConfig} · provider execution: ${status.safety.executesProvider}`,
12
+ ].join('\n');
13
+ }
14
+ export function setupNeedsAction(status) {
15
+ return status.store.status !== 'ready' || status.verification.status !== 'ready' || status.defaults.status === 'blocked';
16
+ }
17
+ function renderDefaults(status) {
18
+ if (status.agents.deferred)
19
+ return 'Project-scoped default manager/profile checks are deferred until a project is selected.';
20
+ if (status.defaults.status !== 'ready')
21
+ return status.defaults.blocker ?? 'default context unavailable';
22
+ return `${status.defaults.agentName} via ${status.defaults.providerAdapter ?? 'unknown provider'} (${status.defaults.model ?? 'unknown model'})`;
23
+ }
24
+ function renderProvider(provider) {
25
+ const safety = provider.actions.some((action) => action.safety.writesProviderConfig)
26
+ ? 'external write action flagged'
27
+ : 'no provider writes';
28
+ return `${provider.displayName} ${provider.status}/${provider.supportLevel} (${safety})`;
29
+ }
@@ -0,0 +1,56 @@
1
+ export function buildSetupWizardReadModel(status) {
2
+ const noProject = status.projectState.mode === 'none';
3
+ const modelStatus = noProject ? 'ready-without-project' : status.verification.status === 'ready' ? 'ready' : 'blocked';
4
+ return {
5
+ status: modelStatus,
6
+ projectMode: noProject ? 'none' : 'selected',
7
+ ...(status.project === undefined ? {} : { project: status.project }),
8
+ summary: noProject
9
+ ? 'Environment and provider setup can continue; project-scoped checks are deferred, not failed.'
10
+ : status.verification.summary,
11
+ steps: [
12
+ { id: 'environment', label: 'Environment', status: readinessToSection(status.environment.status), summary: status.environment.summary },
13
+ { id: 'project', label: 'Project', status: status.projectState.status === 'deferred' ? 'deferred' : readinessToSection(status.projectState.status), summary: status.projectState.summary },
14
+ { id: 'providers', label: 'Providers', status: 'ready', summary: `${status.providers.length} provider targets: ${status.providers.map((provider) => provider.displayName).join(', ')}.` },
15
+ { id: 'agents', label: 'Agents', status: status.agents.deferred ? 'deferred' : readinessToSection(status.agents.status), summary: status.agents.deferred ? 'Project-scoped manager/profile checks are deferred until a project is selected.' : status.agents.blocker ?? 'Default manager/profile context is available.' },
16
+ { id: 'verification', label: 'Verification', status: readinessToSection(status.verification.status), summary: status.verification.summary },
17
+ ],
18
+ providerCards: status.providers.map((provider) => ({
19
+ id: provider.providerId,
20
+ label: provider.displayName,
21
+ supportLevel: provider.supportLevel,
22
+ status: provider.status,
23
+ summary: provider.summary,
24
+ capabilities: provider.capabilities,
25
+ targets: provider.targets,
26
+ guidance: provider.guidance,
27
+ evidence: provider.evidence,
28
+ actions: provider.actions.map((action) => ({ ...action, safetyLabels: safetyLabels(action) })),
29
+ })),
30
+ safetySummary: [
31
+ 'Read model is status/preview data only; renderers can consume it without side effects.',
32
+ 'External or mutating provider actions are data-only and must be copied/run outside the TUI.',
33
+ ],
34
+ };
35
+ }
36
+ function readinessToSection(status) {
37
+ if (status === 'ready')
38
+ return 'ready';
39
+ if (status === 'blocked')
40
+ return 'blocked';
41
+ return 'empty';
42
+ }
43
+ function safetyLabels(action) {
44
+ const labels = [];
45
+ if (action.safety.externalOnly)
46
+ labels.push('External');
47
+ if (action.safety.mutating)
48
+ labels.push('Mutating');
49
+ if (action.safety.writesProviderConfig)
50
+ labels.push('Writes provider config');
51
+ if (action.safety.requiresExplicitConfirmation)
52
+ labels.push('Requires explicit confirmation');
53
+ if (!action.safety.writesProviderConfig && !action.safety.mutating)
54
+ labels.push('No provider writes');
55
+ return labels;
56
+ }
@@ -0,0 +1,148 @@
1
+ import { badge, joinSections, renderActionLine, renderPanel } from './tui-render-helpers.js';
2
+ export function renderSetupWizard(readModel, state, options = {}) {
3
+ if (readModel === undefined) {
4
+ return joinSections([
5
+ renderPanel('Setup · Loading', [
6
+ `${badge('loading')} Setup status is not loaded yet.`,
7
+ 'Provider details will appear after setup status is loaded. Press r to refresh.',
8
+ ], { ...options, status: 'loading' }),
9
+ renderFallbackProgress(state, options),
10
+ ]);
11
+ }
12
+ return joinSections([
13
+ renderSetupSummary(readModel, state, options),
14
+ renderProgress(readModel.steps, state, options),
15
+ renderProviderCards(readModel.providerCards, state, options),
16
+ renderExternalActionsPanel(readModel.providerCards, state, options),
17
+ renderPanel('Setup safety', [shortSafetySummary(readModel.safetySummary)], options),
18
+ ]);
19
+ }
20
+ function renderSetupSummary(readModel, state, options) {
21
+ const activeIndex = Math.max(0, readModel.steps.findIndex((step) => step.id === state.activeStep));
22
+ const activeStep = readModel.steps[activeIndex] ?? readModel.steps[0];
23
+ const title = `Setup · Step ${activeIndex + 1}/5 · ${activeStep?.label ?? 'Unknown'}`;
24
+ return renderPanel(title, [
25
+ `Status: ${badge(readModel.status)}`,
26
+ `Summary: ${readModel.summary}`,
27
+ `Project mode: ${readModel.projectMode}${readModel.project === undefined ? '' : ` (${readModel.project})`}`,
28
+ 'Next safe action: review setup status here, then run any provider commands outside the TUI after review.',
29
+ ], { ...options, status: readModel.status });
30
+ }
31
+ function renderProgress(steps, state, options) {
32
+ return renderPanel('Setup steps', steps.map((step, index) => {
33
+ const current = step.id === state.activeStep ? '› [current]' : ' ';
34
+ const ordinal = `${index + 1}/5`;
35
+ return `${current} ${ordinal} ${step.label} ${badge(step.status)} — ${step.summary}`;
36
+ }), options);
37
+ }
38
+ function renderFallbackProgress(state, options) {
39
+ const labels = ['Environment', 'Project', 'Providers', 'Agents', 'Verification'];
40
+ const ids = ['environment', 'project', 'providers', 'agents', 'verification'];
41
+ return renderPanel('Setup steps', labels.map((label, index) => {
42
+ const current = ids[index] === state.activeStep ? '› [current]' : ' ';
43
+ return `${current} ${index + 1}/5 ${label} ${badge('loading')}`;
44
+ }), options);
45
+ }
46
+ function renderProviderCards(cards, state, options) {
47
+ if (cards.length === 0)
48
+ return renderPanel('Providers', ['Providers: none available yet.'], { ...options, status: 'empty' });
49
+ return renderPanel('Provider cards', [
50
+ ...cards.map((card) => renderProviderRow(card, state)),
51
+ ...renderFocusedProviderDetails(cards, state),
52
+ ], options);
53
+ }
54
+ function renderProviderRow(card, state) {
55
+ const focused = state.focusedProviderId === card.id ? '› [focused]' : ' ';
56
+ const selected = state.selectedProviderIds.includes(card.id) ? '[x]' : '[ ]';
57
+ const support = supportLabel(card.supportLevel);
58
+ const safetyLabels = setupSafetyLabels(card);
59
+ return `${focused} ${selected} ${card.label} ${badge(card.status)} — ${support} ${safetyLabels.map(displaySafetyBadge).join(' ')} — ${card.summary}`;
60
+ }
61
+ function renderFocusedProviderDetails(cards, state) {
62
+ const focusedCard = cards.find((card) => card.id === state.focusedProviderId) ?? cards[state.focusedIndex] ?? cards[0];
63
+ if (focusedCard === undefined)
64
+ return [];
65
+ const targets = focusedCard.targets.length === 0
66
+ ? 'Targets: none'
67
+ : `Targets: ${focusedCard.targets.map((target) => `${target.label} (${target.kind}${target.path === undefined ? '' : `: ${target.path}`}; dashboard-write: no; no-TUI-write)`).join('; ')}`;
68
+ const safetyLabels = setupSafetyLabels(focusedCard);
69
+ return [
70
+ '',
71
+ 'Focused provider details:',
72
+ ` ${focusedCard.label}: ${focusedCard.summary}`,
73
+ ` Safety: ${safetyLabels.map(displaySafetyBadge).join(' ')}`,
74
+ ` ${targets}`,
75
+ ` Capabilities: ${focusedCard.capabilities.length === 0 ? 'none' : focusedCard.capabilities.join(', ')}`,
76
+ ...(focusedCard.guidance.length === 0 ? [] : [` Guidance: ${focusedCard.guidance.join(' ')}`]),
77
+ ...(state.expandedProviderId === focusedCard.id && focusedCard.evidence.length > 0 ? [
78
+ ' Evidence (presentational only):',
79
+ ...focusedCard.evidence.map((item) => ` - ${item}`),
80
+ ] : []),
81
+ ];
82
+ }
83
+ function renderExternalActionsPanel(cards, state, options) {
84
+ const focusedCard = cards.find((card) => card.id === state.focusedProviderId) ?? cards[state.focusedIndex] ?? cards[0];
85
+ const focusedAction = focusedCard?.actions[0];
86
+ const actionLines = cards.flatMap((card) => card.actions.flatMap((action) => renderActionLine(`${card.label}: ${action.label}`, {
87
+ ...options,
88
+ labels: action.safetyLabels.map(normalizeSafetyLabel),
89
+ description: compactActionDescription(action.kind),
90
+ })));
91
+ return renderPanel('External actions', [
92
+ 'Compact external-only guidance: [external] [no TUI writes]; provider config writes require confirmation outside the TUI.',
93
+ ...(actionLines.length === 0 ? ['- No external actions available.'] : actionLines),
94
+ ...(focusedCard === undefined || focusedAction === undefined ? [] : renderFocusedActionDetails(focusedCard, focusedAction)),
95
+ ], options);
96
+ }
97
+ function renderFocusedActionDetails(card, action) {
98
+ return [
99
+ '',
100
+ `Focused action details: ${card.label}: ${action.label} ${action.safetyLabels.map(displaySafetyBadge).join(' ')}`,
101
+ ` Details: ${action.description.length === 0 ? `${action.kind}; run outside the TUI after review.` : action.description}`,
102
+ ...(action.command === undefined || action.command.length === 0 ? [] : [` Command: ${action.command.join(' ')}`]),
103
+ ];
104
+ }
105
+ function compactActionDescription(kind) {
106
+ return `${kind}; external guidance only; no TUI writes.`;
107
+ }
108
+ function shortSafetySummary(items) {
109
+ if (items.length === 0)
110
+ return 'Read-only setup preview: [no TUI writes]; external actions are [external] and require review.';
111
+ return 'Read-only setup preview: [no TUI writes]; external actions are [external] and require review outside the TUI.';
112
+ }
113
+ function supportLabel(level) {
114
+ if (level === 'supported-primary')
115
+ return 'Supported primary';
116
+ if (level === 'preview-only')
117
+ return 'Preview-only';
118
+ if (level === 'placeholder')
119
+ return 'Placeholder';
120
+ if (level === 'extension-point')
121
+ return 'Extension point';
122
+ return 'Unsupported';
123
+ }
124
+ function setupSafetyLabels(card) {
125
+ return unique(['No TUI writes', ...card.actions.flatMap((action) => action.safetyLabels)].map(normalizeSafetyLabel));
126
+ }
127
+ function unique(values) {
128
+ return [...new Set(values)];
129
+ }
130
+ function displaySafetyBadge(value) {
131
+ return badge(normalizeSafetyLabel(value));
132
+ }
133
+ function normalizeSafetyLabel(value) {
134
+ const normalized = value.trim().toLowerCase();
135
+ if (normalized === 'external')
136
+ return 'external';
137
+ if (normalized === 'mutating')
138
+ return 'mutating';
139
+ if (normalized === 'writes provider config')
140
+ return 'writes provider config';
141
+ if (normalized === 'no tui writes')
142
+ return 'no TUI writes';
143
+ if (normalized === 'requires explicit confirmation')
144
+ return 'requires confirmation';
145
+ if (normalized === 'no provider writes')
146
+ return 'no TUI writes';
147
+ return value;
148
+ }
@@ -0,0 +1,82 @@
1
+ export const setupWizardSteps = ['environment', 'project', 'providers', 'agents', 'verification'];
2
+ export const defaultSetupProviderIds = ['opencode', 'claude', 'antigravity', 'custom'];
3
+ export function createSetupWizardState(input = {}) {
4
+ const selectedProviderIds = [...(input.selectedProviderIds ?? ['opencode'])];
5
+ const focusedProviderId = input.focusedProviderId ?? defaultSetupProviderIds[0];
6
+ return {
7
+ activeStep: input.activeStep ?? 'environment',
8
+ focusedIndex: clampFocus(input.focusedIndex ?? 0),
9
+ projectMode: input.projectMode ?? 'none',
10
+ selectedProviderIds,
11
+ ...(input.selectedProject === undefined ? {} : { selectedProject: input.selectedProject }),
12
+ ...(focusedProviderId === undefined ? {} : { focusedProviderId }),
13
+ ...(input.expandedProviderId === undefined ? {} : { expandedProviderId: input.expandedProviderId }),
14
+ };
15
+ }
16
+ export function setupWizardKeyFromInput(input) {
17
+ if (input === 'n')
18
+ return 'next';
19
+ if (input === 'b')
20
+ return 'back';
21
+ if (input === ' ')
22
+ return 'toggle';
23
+ if (input === '\r' || input === '\n')
24
+ return 'details';
25
+ if (input === '\u001B')
26
+ return 'escape';
27
+ if (input === 'k' || input === '\u001B[A')
28
+ return 'up';
29
+ if (input === 'j' || input === '\u001B[B')
30
+ return 'down';
31
+ return undefined;
32
+ }
33
+ export function reduceSetupWizardKey(state, key) {
34
+ if (key === 'next')
35
+ return moveStep(state, 1);
36
+ if (key === 'back')
37
+ return moveStep(state, -1);
38
+ if (key === 'up')
39
+ return moveFocus(state, -1);
40
+ if (key === 'down')
41
+ return moveFocus(state, 1);
42
+ if (key === 'toggle')
43
+ return state.activeStep === 'providers' ? toggleFocusedProvider(state) : state;
44
+ if (key === 'details')
45
+ return state.activeStep === 'providers' ? toggleExpandedProvider(state) : state;
46
+ if (key === 'escape')
47
+ return state.expandedProviderId === undefined ? moveStep(state, -1) : withoutExpandedProvider(state);
48
+ return state;
49
+ }
50
+ function moveStep(state, delta) {
51
+ const index = setupWizardSteps.indexOf(state.activeStep);
52
+ const nextIndex = Math.max(0, Math.min(index + delta, setupWizardSteps.length - 1));
53
+ const activeStep = setupWizardSteps[nextIndex] ?? state.activeStep;
54
+ return activeStep === state.activeStep ? state : { ...state, activeStep };
55
+ }
56
+ function moveFocus(state, delta) {
57
+ const focusedIndex = clampFocus(state.focusedIndex + delta);
58
+ if (state.activeStep !== 'providers')
59
+ return focusedIndex === state.focusedIndex ? state : { ...state, focusedIndex };
60
+ const focusedProviderId = defaultSetupProviderIds[focusedIndex] ?? state.focusedProviderId;
61
+ return { ...state, focusedIndex, ...(focusedProviderId === undefined ? {} : { focusedProviderId }) };
62
+ }
63
+ function toggleFocusedProvider(state) {
64
+ const providerId = state.focusedProviderId ?? defaultSetupProviderIds[state.focusedIndex];
65
+ if (providerId === undefined)
66
+ return state;
67
+ const selected = state.selectedProviderIds.includes(providerId);
68
+ return { ...state, selectedProviderIds: selected ? state.selectedProviderIds.filter((id) => id !== providerId) : [...state.selectedProviderIds, providerId] };
69
+ }
70
+ function toggleExpandedProvider(state) {
71
+ const providerId = state.focusedProviderId ?? defaultSetupProviderIds[state.focusedIndex];
72
+ if (providerId === undefined)
73
+ return state;
74
+ return state.expandedProviderId === providerId ? withoutExpandedProvider(state) : { ...state, expandedProviderId: providerId };
75
+ }
76
+ function withoutExpandedProvider(state) {
77
+ const { expandedProviderId: _expandedProviderId, ...rest } = state;
78
+ return rest;
79
+ }
80
+ function clampFocus(index) {
81
+ return Math.max(0, Math.min(index, defaultSetupProviderIds.length - 1));
82
+ }
@@ -0,0 +1,192 @@
1
+ export const DEFAULT_TUI_WIDTH = 100;
2
+ export const MIN_TUI_WIDTH = 40;
3
+ const knownBadges = new Set([
4
+ 'ready',
5
+ 'deferred',
6
+ 'blocked',
7
+ 'loading',
8
+ 'error',
9
+ 'empty',
10
+ 'external',
11
+ 'mutating',
12
+ 'no tui writes',
13
+ 'no provider writes',
14
+ 'writes provider config',
15
+ 'requires confirmation',
16
+ 'available',
17
+ 'preview-only',
18
+ 'manual',
19
+ 'coming-soon',
20
+ 'unsupported',
21
+ 'ready-without-project',
22
+ 'project-required',
23
+ ]);
24
+ export function normalizeRenderWidth(width) {
25
+ if (width === undefined || !Number.isFinite(width))
26
+ return DEFAULT_TUI_WIDTH;
27
+ return Math.max(MIN_TUI_WIDTH, Math.floor(width));
28
+ }
29
+ export function stripAnsi(value) {
30
+ return value.replace(/\u001B\[[0-?]*[ -/]*[@-~]/g, '');
31
+ }
32
+ export function visibleLength(value) {
33
+ return stripAnsi(value).length;
34
+ }
35
+ export function badge(value, options = {}) {
36
+ const normalized = value.trim().replace(/[_\s]+/g, ' ').toLowerCase();
37
+ if (normalized.length === 0)
38
+ return styleBadge('[unknown]', 'unknown', options);
39
+ if (knownBadges.has(normalized)) {
40
+ const label = normalized === 'no tui writes' ? 'no TUI writes' : normalized;
41
+ return styleBadge(`[${label}]`, normalized, options);
42
+ }
43
+ return styleBadge(`[unknown: ${normalized}]`, 'unknown', options);
44
+ }
45
+ export function wrapText(text, options = {}) {
46
+ const width = normalizeRenderWidth(options.width);
47
+ const indent = options.indent ?? '';
48
+ const hangingIndent = options.hangingIndent ?? indent;
49
+ const firstAvailable = Math.max(1, width - visibleLength(indent));
50
+ const hangingAvailable = Math.max(1, width - visibleLength(hangingIndent));
51
+ const words = text.trim().length === 0 ? [''] : text.trim().split(/\s+/);
52
+ const lines = [];
53
+ let currentIndent = indent;
54
+ let available = firstAvailable;
55
+ let current = '';
56
+ for (const word of words) {
57
+ if (current.length === 0) {
58
+ current = word;
59
+ continue;
60
+ }
61
+ if (visibleLength(`${current} ${word}`) <= available) {
62
+ current = `${current} ${word}`;
63
+ continue;
64
+ }
65
+ lines.push(`${currentIndent}${current}`);
66
+ currentIndent = hangingIndent;
67
+ available = hangingAvailable;
68
+ current = word;
69
+ }
70
+ lines.push(`${currentIndent}${current}`);
71
+ return lines;
72
+ }
73
+ export function renderPanel(title, bodyLines, options = {}) {
74
+ const width = normalizeRenderWidth(options.width);
75
+ const contentWidth = Math.max(1, width - 4);
76
+ const status = options.status === undefined ? '' : ` ${badge(options.status, options)}`;
77
+ const titleText = ansi(options, 'title', `${title}${status}`);
78
+ const top = borderedTop(titleText, width, options);
79
+ const body = bodyLines.flatMap((line) => line.split('\n')).flatMap((line) => wrapPanelLine(styleKnownBadges(line, options), contentWidth, options));
80
+ const footer = (options.footerLines ?? []).flatMap((line) => wrapPanelLine(styleKnownBadges(line, options), contentWidth, options));
81
+ return [top, ...[...body, ...footer].map((line) => borderedBody(line, width, options)), borderedBottom(width, options)];
82
+ }
83
+ export function styleFocusedRow(value, options = {}) {
84
+ return ansi(options, 'highlight', value);
85
+ }
86
+ export function renderTabRow(items, options = {}) {
87
+ const width = normalizeRenderWidth(options.width);
88
+ const tokens = items.map((item) => renderTabToken(item, options));
89
+ return wrapTokens('Tabs:', tokens, width);
90
+ }
91
+ function renderTabToken(item, options) {
92
+ if (item.selected && item.focused === true)
93
+ return ansi(options, 'highlight', `› [${item.key}] ${item.label} [selected] [focused]`);
94
+ if (item.selected)
95
+ return ansi(options, 'highlight', `› [${item.key}] ${item.label} [selected]`);
96
+ if (item.focused === true)
97
+ return ansi(options, 'focus', `» [${item.key}] ${item.label} [focused]`);
98
+ return ansi(options, 'dim', `[${item.key}] ${item.label}`);
99
+ }
100
+ export function renderFooterGroups(groups, options = {}) {
101
+ const width = normalizeRenderWidth(options.width);
102
+ const lines = [];
103
+ for (const group of groups) {
104
+ lines.push(...wrapTokens(`${group.label}:`, group.items, width));
105
+ }
106
+ return lines;
107
+ }
108
+ export function renderActionLine(label, options = {}) {
109
+ const labels = (options.labels ?? []).map((value) => badge(value)).join(' ');
110
+ const primary = `- ${label}${labels.length === 0 ? '' : ` ${labels}`}`;
111
+ return [
112
+ ...wrapText(primary, options),
113
+ ...(options.description === undefined ? [] : wrapText(options.description, { ...options, indent: ' ', hangingIndent: ' ' })),
114
+ ...(options.command === undefined || options.command.length === 0 ? [] : wrapText(`Command: ${options.command.join(' ')}`, { ...options, indent: ' ', hangingIndent: ' ' })),
115
+ ];
116
+ }
117
+ export function joinSections(sections) {
118
+ return sections
119
+ .map((section) => typeof section === 'string' || section === undefined ? section?.trim() : section.join('\n').trim())
120
+ .filter((section) => section !== undefined && section.length > 0)
121
+ .join('\n\n');
122
+ }
123
+ function wrapTokens(prefix, tokens, width) {
124
+ const lines = [];
125
+ let current = prefix;
126
+ for (const token of tokens) {
127
+ const separator = current === prefix ? ' ' : ' · ';
128
+ const candidate = `${current}${separator}${token}`;
129
+ if (visibleLength(candidate) <= width || current === prefix) {
130
+ current = candidate;
131
+ continue;
132
+ }
133
+ lines.push(current);
134
+ current = `${' '.repeat(prefix.length)} ${token}`;
135
+ }
136
+ lines.push(current);
137
+ return lines;
138
+ }
139
+ function wrapPanelLine(line, contentWidth, options) {
140
+ if (visibleLength(line) <= contentWidth)
141
+ return [line];
142
+ return wrapText(line, { ...options, width: contentWidth });
143
+ }
144
+ function borderedTop(title, width, options) {
145
+ const prefix = `┌─ ${title} `;
146
+ return `${ansi(options, 'border', prefix)}${ansi(options, 'border', '─'.repeat(Math.max(0, width - visibleLength(prefix) - 1)))}${ansi(options, 'border', '┐')}`;
147
+ }
148
+ function borderedBody(line, width, options) {
149
+ const padded = `${line}${' '.repeat(Math.max(0, width - visibleLength(line) - 4))}`;
150
+ return `${ansi(options, 'border', '│')} ${padded} ${ansi(options, 'border', '│')}`;
151
+ }
152
+ function borderedBottom(width, options) {
153
+ return ansi(options, 'border', `└${'─'.repeat(Math.max(0, width - 2))}┘`);
154
+ }
155
+ function styleKnownBadges(line, options) {
156
+ if (options.style !== 'ansi')
157
+ return line;
158
+ return line.replace(/\[([^\]]+)\]/g, (match, raw) => {
159
+ const normalized = raw.trim().replace(/[_\s]+/g, ' ').toLowerCase();
160
+ return knownBadges.has(normalized) ? styleBadge(match, normalized, options) : match;
161
+ });
162
+ }
163
+ function styleBadge(value, status, options) {
164
+ if (options.style !== 'ansi')
165
+ return value;
166
+ if (['ready', 'available', 'ready-without-project'].includes(status))
167
+ return ansi(options, 'green', value);
168
+ if (['blocked', 'error', 'mutating', 'writes provider config'].includes(status))
169
+ return ansi(options, 'red', value);
170
+ if (['deferred', 'loading', 'manual', 'requires confirmation', 'preview-only', 'coming-soon', 'project-required'].includes(status))
171
+ return ansi(options, 'yellow', value);
172
+ if (['external', 'no tui writes', 'no provider writes'].includes(status))
173
+ return ansi(options, 'cyan', value);
174
+ return ansi(options, 'dim', value);
175
+ }
176
+ function ansi(options, role, value) {
177
+ if (options.style !== 'ansi')
178
+ return value;
179
+ const codes = {
180
+ bold: '1',
181
+ title: '1;96',
182
+ dim: '2',
183
+ highlight: '1;7;36',
184
+ focus: '1;36',
185
+ border: '36',
186
+ green: '32',
187
+ red: '31',
188
+ yellow: '33',
189
+ cyan: '36',
190
+ };
191
+ return `\u001B[${codes[role]}m${value}\u001B[0m`;
192
+ }
@@ -0,0 +1,71 @@
1
+ export const redactedPlaceholder = '[REDACTED]';
2
+ const sensitiveKeyParts = [
3
+ 'token',
4
+ 'apikey',
5
+ 'secret',
6
+ 'password',
7
+ 'authorization',
8
+ 'credential',
9
+ 'privatekey',
10
+ 'session',
11
+ 'cookie',
12
+ ];
13
+ const sensitiveStringPatterns = [
14
+ /\b(?:bearer|basic)\s+[A-Za-z0-9._~+/=-]+\b/i,
15
+ /\bsk-[A-Za-z0-9_-]{8,}\b/i,
16
+ /\b[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\b/,
17
+ /\bgh[pousr]_[A-Za-z0-9_]{20,}\b/,
18
+ ];
19
+ export function redactJsonValue(value) {
20
+ if (Array.isArray(value))
21
+ return value.map((item) => redactJsonValue(item));
22
+ if (isJsonObject(value))
23
+ return redactJsonObject(value);
24
+ if (typeof value === 'string')
25
+ return redactString(value);
26
+ return value;
27
+ }
28
+ function redactJsonObject(value) {
29
+ return Object.fromEntries(Object.entries(value).map(([key, child]) => [
30
+ key,
31
+ isSensitiveKey(key) ? redactedPlaceholder : redactJsonValue(child),
32
+ ]));
33
+ }
34
+ function redactString(value) {
35
+ const parsed = parseJsonString(value);
36
+ if (parsed !== undefined)
37
+ return JSON.stringify(redactJsonValue(parsed));
38
+ if (sensitiveStringPatterns.some((pattern) => pattern.test(value)))
39
+ return redactedPlaceholder;
40
+ return value;
41
+ }
42
+ function parseJsonString(value) {
43
+ const trimmed = value.trim();
44
+ if (!trimmed.startsWith('{') && !trimmed.startsWith('['))
45
+ return undefined;
46
+ try {
47
+ const parsed = JSON.parse(value);
48
+ return isJsonValue(parsed) ? parsed : undefined;
49
+ }
50
+ catch {
51
+ return undefined;
52
+ }
53
+ }
54
+ function isSensitiveKey(key) {
55
+ const normalized = key.toLowerCase().replaceAll(/[^a-z0-9]/g, '');
56
+ return sensitiveKeyParts.some((part) => normalized.includes(part));
57
+ }
58
+ function isJsonObject(value) {
59
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
60
+ }
61
+ function isJsonValue(value) {
62
+ if (value === null)
63
+ return true;
64
+ if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean')
65
+ return true;
66
+ if (Array.isArray(value))
67
+ return value.every(isJsonValue);
68
+ if (typeof value === 'object')
69
+ return Object.values(value).every(isJsonValue);
70
+ return false;
71
+ }