@nexus-ai-fs/tui 0.9.18
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 +30 -0
- package/package.json +48 -0
- package/src/app.tsx +349 -0
- package/src/index.tsx +137 -0
- package/src/opentui-env.d.ts +61 -0
- package/src/panels/access/access-panel.tsx +597 -0
- package/src/panels/access/alert-list.tsx +77 -0
- package/src/panels/access/constraint-creator.tsx +128 -0
- package/src/panels/access/constraint-list.tsx +72 -0
- package/src/panels/access/credential-list.tsx +68 -0
- package/src/panels/access/delegation-chain-view.tsx +110 -0
- package/src/panels/access/delegation-completer.tsx +120 -0
- package/src/panels/access/delegation-creator.tsx +237 -0
- package/src/panels/access/delegation-list.tsx +74 -0
- package/src/panels/access/fraud-score-view.tsx +94 -0
- package/src/panels/access/manifest-creator.tsx +167 -0
- package/src/panels/access/manifest-list.tsx +105 -0
- package/src/panels/access/namespace-config-view.tsx +525 -0
- package/src/panels/access/permission-checker.tsx +231 -0
- package/src/panels/agents/agent-status-view.tsx +196 -0
- package/src/panels/agents/agents-panel.tsx +493 -0
- package/src/panels/agents/delegation-list.tsx +154 -0
- package/src/panels/agents/inbox-view.tsx +96 -0
- package/src/panels/agents/trajectories-tab.tsx +40 -0
- package/src/panels/api-console/api-console-panel.tsx +189 -0
- package/src/panels/api-console/codegen-viewer.tsx +36 -0
- package/src/panels/api-console/codegen.ts +112 -0
- package/src/panels/api-console/endpoint-list.tsx +57 -0
- package/src/panels/api-console/request-builder.tsx +69 -0
- package/src/panels/api-console/response-viewer.tsx +54 -0
- package/src/panels/connectors/available-tab.tsx +357 -0
- package/src/panels/connectors/connector-row.tsx +121 -0
- package/src/panels/connectors/connectors-panel.tsx +88 -0
- package/src/panels/connectors/error-parser.ts +116 -0
- package/src/panels/connectors/mounted-tab.tsx +179 -0
- package/src/panels/connectors/skills-tab.tsx +235 -0
- package/src/panels/connectors/template-generator.ts +211 -0
- package/src/panels/connectors/write-tab.tsx +514 -0
- package/src/panels/events/audit-tab.tsx +69 -0
- package/src/panels/events/audit-trail.tsx +75 -0
- package/src/panels/events/connector-detail.tsx +49 -0
- package/src/panels/events/connector-list.tsx +73 -0
- package/src/panels/events/connectors-tab.tsx +92 -0
- package/src/panels/events/event-replay.tsx +80 -0
- package/src/panels/events/events-panel.tsx +414 -0
- package/src/panels/events/events-tab.tsx +212 -0
- package/src/panels/events/lock-list.tsx +54 -0
- package/src/panels/events/locks-tab.tsx +103 -0
- package/src/panels/events/mcl-replay.tsx +77 -0
- package/src/panels/events/mcl-tab.tsx +83 -0
- package/src/panels/events/operations-tab-wrapper.tsx +62 -0
- package/src/panels/events/operations-tab.tsx +41 -0
- package/src/panels/events/replay-tab.tsx +76 -0
- package/src/panels/events/secrets-audit.tsx +64 -0
- package/src/panels/events/secrets-tab.tsx +75 -0
- package/src/panels/events/subscription-list.tsx +54 -0
- package/src/panels/events/subscriptions-tab.tsx +82 -0
- package/src/panels/files/file-aspects.tsx +93 -0
- package/src/panels/files/file-editor.tsx +160 -0
- package/src/panels/files/file-explorer-keybindings.ts +468 -0
- package/src/panels/files/file-explorer-panel.tsx +545 -0
- package/src/panels/files/file-lineage.tsx +163 -0
- package/src/panels/files/file-list-item.tsx +28 -0
- package/src/panels/files/file-metadata.tsx +62 -0
- package/src/panels/files/file-preview.tsx +108 -0
- package/src/panels/files/file-schema.tsx +89 -0
- package/src/panels/files/file-tree-node.tsx +44 -0
- package/src/panels/files/file-tree.tsx +169 -0
- package/src/panels/files/share-links-tab.tsx +33 -0
- package/src/panels/files/uploads-tab.tsx +45 -0
- package/src/panels/payments/approval-list.tsx +83 -0
- package/src/panels/payments/balance-card.tsx +43 -0
- package/src/panels/payments/budget-card.tsx +70 -0
- package/src/panels/payments/payments-panel.tsx +451 -0
- package/src/panels/payments/policy-list.tsx +64 -0
- package/src/panels/payments/reservation-list.tsx +78 -0
- package/src/panels/payments/transaction-list.tsx +103 -0
- package/src/panels/payments/transfer-form.tsx +109 -0
- package/src/panels/search/column-search.tsx +79 -0
- package/src/panels/search/knowledge-view.tsx +100 -0
- package/src/panels/search/memory-list.tsx +197 -0
- package/src/panels/search/playbook-list.tsx +77 -0
- package/src/panels/search/rlm-answer-view.tsx +105 -0
- package/src/panels/search/search-panel.tsx +405 -0
- package/src/panels/search/search-results.tsx +116 -0
- package/src/panels/stack/stack-panel.tsx +474 -0
- package/src/panels/versions/conflicts-tab.tsx +59 -0
- package/src/panels/versions/entry-detail.tsx +89 -0
- package/src/panels/versions/transaction-actions.tsx +34 -0
- package/src/panels/versions/transaction-list.tsx +90 -0
- package/src/panels/versions/versions-panel.tsx +276 -0
- package/src/panels/workflows/execution-list.tsx +102 -0
- package/src/panels/workflows/scheduler-view.tsx +135 -0
- package/src/panels/workflows/workflow-list.tsx +88 -0
- package/src/panels/workflows/workflows-panel.tsx +295 -0
- package/src/panels/zones/brick-detail.tsx +136 -0
- package/src/panels/zones/brick-list.tsx +56 -0
- package/src/panels/zones/cache-tab.tsx +118 -0
- package/src/panels/zones/drift-view.tsx +97 -0
- package/src/panels/zones/mcp-mounts-tab.tsx +38 -0
- package/src/panels/zones/memories-tab.tsx +37 -0
- package/src/panels/zones/reindex-status.tsx +84 -0
- package/src/panels/zones/workspaces-tab.tsx +37 -0
- package/src/panels/zones/zone-list.tsx +73 -0
- package/src/panels/zones/zones-panel.tsx +559 -0
- package/src/services/command-runner.ts +303 -0
- package/src/shared/accessibility-announcements.ts +44 -0
- package/src/shared/action-registry.ts +466 -0
- package/src/shared/brick-states.ts +91 -0
- package/src/shared/command-palette.ts +35 -0
- package/src/shared/components/announcement-bar.tsx +30 -0
- package/src/shared/components/app-confirm-dialog.tsx +29 -0
- package/src/shared/components/breadcrumb.tsx +21 -0
- package/src/shared/components/brick-gate.tsx +60 -0
- package/src/shared/components/command-output.tsx +95 -0
- package/src/shared/components/command-palette.tsx +97 -0
- package/src/shared/components/confirm-dialog.tsx +61 -0
- package/src/shared/components/diff-viewer.tsx +219 -0
- package/src/shared/components/empty-state.tsx +36 -0
- package/src/shared/components/error-bar.tsx +60 -0
- package/src/shared/components/error-boundary.tsx +53 -0
- package/src/shared/components/help-overlay.tsx +99 -0
- package/src/shared/components/identity-switcher.tsx +168 -0
- package/src/shared/components/loading-indicator.tsx +40 -0
- package/src/shared/components/pagination-bar.tsx +68 -0
- package/src/shared/components/pre-connection-screen.tsx +398 -0
- package/src/shared/components/scroll-indicator.tsx +46 -0
- package/src/shared/components/side-nav-utils.ts +68 -0
- package/src/shared/components/side-nav.tsx +287 -0
- package/src/shared/components/spinner.tsx +26 -0
- package/src/shared/components/status-bar.tsx +117 -0
- package/src/shared/components/styled-text.tsx +72 -0
- package/src/shared/components/sub-tab-bar-utils.ts +100 -0
- package/src/shared/components/sub-tab-bar.tsx +40 -0
- package/src/shared/components/tab-bar-utils.ts +36 -0
- package/src/shared/components/tab-bar.tsx +50 -0
- package/src/shared/components/text-input.tsx +73 -0
- package/src/shared/components/tooltip.tsx +53 -0
- package/src/shared/components/virtual-list.tsx +93 -0
- package/src/shared/components/welcome-screen.tsx +111 -0
- package/src/shared/hooks/use-api.ts +10 -0
- package/src/shared/hooks/use-brick-available.ts +42 -0
- package/src/shared/hooks/use-confirm.ts +66 -0
- package/src/shared/hooks/use-connection-state.ts +67 -0
- package/src/shared/hooks/use-copy.ts +31 -0
- package/src/shared/hooks/use-fresh-server.ts +62 -0
- package/src/shared/hooks/use-keyboard.ts +58 -0
- package/src/shared/hooks/use-list-navigation.ts +106 -0
- package/src/shared/hooks/use-swr.ts +117 -0
- package/src/shared/hooks/use-tab-fallback.ts +32 -0
- package/src/shared/hooks/use-text-input.ts +113 -0
- package/src/shared/hooks/use-visible-tabs.ts +61 -0
- package/src/shared/lib/circular-buffer.ts +82 -0
- package/src/shared/lib/clipboard.ts +14 -0
- package/src/shared/nav-items.ts +73 -0
- package/src/shared/navigation.ts +110 -0
- package/src/shared/status-breadcrumb.ts +74 -0
- package/src/shared/syntax-style.ts +3 -0
- package/src/shared/tab-visibility.ts +15 -0
- package/src/shared/text-style.ts +23 -0
- package/src/shared/theme.ts +179 -0
- package/src/shared/utils/format-size.ts +20 -0
- package/src/shared/utils/format-text.ts +10 -0
- package/src/shared/utils/format-time.ts +72 -0
- package/src/shared/utils/lru-cache.ts +75 -0
- package/src/stores/access-store-types.ts +154 -0
- package/src/stores/access-store.ts +674 -0
- package/src/stores/agents-store.ts +404 -0
- package/src/stores/announcement-store.ts +46 -0
- package/src/stores/api-console-store.ts +476 -0
- package/src/stores/connectors-store.ts +434 -0
- package/src/stores/create-api-action.ts +140 -0
- package/src/stores/delegation-store.ts +300 -0
- package/src/stores/error-store.ts +102 -0
- package/src/stores/events-store.ts +163 -0
- package/src/stores/files-store.ts +630 -0
- package/src/stores/first-run-store.ts +34 -0
- package/src/stores/global-store.ts +255 -0
- package/src/stores/infra-store.ts +461 -0
- package/src/stores/knowledge-store.ts +358 -0
- package/src/stores/lineage-store.ts +126 -0
- package/src/stores/mcp-store.ts +147 -0
- package/src/stores/payments-store.ts +545 -0
- package/src/stores/search-store-types.ts +155 -0
- package/src/stores/search-store.ts +656 -0
- package/src/stores/share-link-store.ts +151 -0
- package/src/stores/stack-store.ts +352 -0
- package/src/stores/ui-store.ts +161 -0
- package/src/stores/upload-store.ts +131 -0
- package/src/stores/versions-store.ts +355 -0
- package/src/stores/workflows-store.ts +402 -0
- package/src/stores/workspace-store.ts +185 -0
- package/src/stores/zones-store.ts +378 -0
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CommandRunner — executes local `nexus` CLI subcommands via Bun.spawn().
|
|
3
|
+
*
|
|
4
|
+
* Decisions implemented:
|
|
5
|
+
* 2A: Shell out to the Python `nexus` binary
|
|
6
|
+
* 4A: Strict allowlist (validated in parseCommand, enforced here as defense-in-depth)
|
|
7
|
+
* 6A: Process lifecycle management with cleanup on shutdown
|
|
8
|
+
* 7A: Accumulator buffer for streaming output
|
|
9
|
+
* 13A+C: Windowed rendering (last MAX_OUTPUT_LINES) + throttled state updates
|
|
10
|
+
* 15A: Show spinner immediately (handled by consumer component)
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { create } from "zustand";
|
|
14
|
+
|
|
15
|
+
// =============================================================================
|
|
16
|
+
// Constants
|
|
17
|
+
// =============================================================================
|
|
18
|
+
|
|
19
|
+
/** Maximum lines retained in the output buffer (Decision 13A). */
|
|
20
|
+
const MAX_OUTPUT_LINES = 200;
|
|
21
|
+
|
|
22
|
+
/** Minimum interval between state updates in ms (Decision 13C). */
|
|
23
|
+
const THROTTLE_MS = 100;
|
|
24
|
+
|
|
25
|
+
/** Defense-in-depth: re-validate the subcommand even though parseCommand already checks. */
|
|
26
|
+
const ALLOWED_COMMANDS = new Set(["init", "build", "demo", "brick", "agent", "up"]);
|
|
27
|
+
|
|
28
|
+
// =============================================================================
|
|
29
|
+
// Types
|
|
30
|
+
// =============================================================================
|
|
31
|
+
|
|
32
|
+
export type CommandStatus = "idle" | "running" | "success" | "error";
|
|
33
|
+
|
|
34
|
+
export interface CommandRunnerState {
|
|
35
|
+
/** Current command status. */
|
|
36
|
+
readonly status: CommandStatus;
|
|
37
|
+
/** Output lines (windowed to last MAX_OUTPUT_LINES). */
|
|
38
|
+
readonly outputLines: readonly string[];
|
|
39
|
+
/** Exit code of the last command (null while running). */
|
|
40
|
+
readonly exitCode: number | null;
|
|
41
|
+
/** The command string being/was executed. */
|
|
42
|
+
readonly commandLabel: string;
|
|
43
|
+
/** Error message if the command failed to spawn. */
|
|
44
|
+
readonly spawnError: string | null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface CommandRunnerStore extends CommandRunnerState {
|
|
48
|
+
readonly appendOutput: (chunk: string) => void;
|
|
49
|
+
readonly setStatus: (status: CommandStatus, exitCode?: number | null) => void;
|
|
50
|
+
readonly setSpawnError: (error: string) => void;
|
|
51
|
+
readonly reset: () => void;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// =============================================================================
|
|
55
|
+
// Store
|
|
56
|
+
// =============================================================================
|
|
57
|
+
|
|
58
|
+
const INITIAL_STATE: CommandRunnerState = {
|
|
59
|
+
status: "idle",
|
|
60
|
+
outputLines: [],
|
|
61
|
+
exitCode: null,
|
|
62
|
+
commandLabel: "",
|
|
63
|
+
spawnError: null,
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
export const useCommandRunnerStore = create<CommandRunnerStore>((set) => ({
|
|
67
|
+
...INITIAL_STATE,
|
|
68
|
+
|
|
69
|
+
appendOutput: (chunk) => {
|
|
70
|
+
set((state) => {
|
|
71
|
+
// Split chunk into lines, preserving partial last line
|
|
72
|
+
const newLines = chunk.split("\n");
|
|
73
|
+
const combined = [...state.outputLines];
|
|
74
|
+
|
|
75
|
+
// Append first fragment to the last existing line (handles partial lines)
|
|
76
|
+
if (combined.length > 0 && newLines.length > 0) {
|
|
77
|
+
combined[combined.length - 1] = combined[combined.length - 1]! + newLines[0]!;
|
|
78
|
+
newLines.shift();
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
combined.push(...newLines);
|
|
82
|
+
|
|
83
|
+
// Window to last MAX_OUTPUT_LINES (Decision 13A)
|
|
84
|
+
const windowed = combined.length > MAX_OUTPUT_LINES
|
|
85
|
+
? combined.slice(-MAX_OUTPUT_LINES)
|
|
86
|
+
: combined;
|
|
87
|
+
|
|
88
|
+
return { outputLines: windowed };
|
|
89
|
+
});
|
|
90
|
+
},
|
|
91
|
+
|
|
92
|
+
setStatus: (status, exitCode) => {
|
|
93
|
+
set({ status, exitCode: exitCode ?? null });
|
|
94
|
+
},
|
|
95
|
+
|
|
96
|
+
setSpawnError: (error) => {
|
|
97
|
+
set({ status: "error", spawnError: error });
|
|
98
|
+
},
|
|
99
|
+
|
|
100
|
+
reset: () => {
|
|
101
|
+
set(INITIAL_STATE);
|
|
102
|
+
},
|
|
103
|
+
}));
|
|
104
|
+
|
|
105
|
+
// =============================================================================
|
|
106
|
+
// Process management (Decision 6A)
|
|
107
|
+
// =============================================================================
|
|
108
|
+
|
|
109
|
+
/** Set of currently running child processes for cleanup on shutdown. */
|
|
110
|
+
const activeProcesses = new Set<{ kill(): void }>();
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Kill all running child processes. Called from the shutdown handler.
|
|
114
|
+
*/
|
|
115
|
+
export function killAllProcesses(): void {
|
|
116
|
+
for (const proc of activeProcesses) {
|
|
117
|
+
try {
|
|
118
|
+
proc.kill();
|
|
119
|
+
} catch {
|
|
120
|
+
// Process may have already exited
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
activeProcesses.clear();
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// =============================================================================
|
|
127
|
+
// Execute local command
|
|
128
|
+
// =============================================================================
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Find the project root by walking up from CWD looking for .git (file or dir).
|
|
132
|
+
* This resolves correctly for both normal repos and git worktrees.
|
|
133
|
+
* Falls back to CWD if not found.
|
|
134
|
+
*/
|
|
135
|
+
function findProjectRoot(): string {
|
|
136
|
+
const path = require("node:path");
|
|
137
|
+
const nodeFs = require("node:fs");
|
|
138
|
+
let dir = process.cwd();
|
|
139
|
+
for (let i = 0; i < 20; i++) {
|
|
140
|
+
if (nodeFs.existsSync(path.join(dir, ".git"))) return dir;
|
|
141
|
+
const parent = path.dirname(dir);
|
|
142
|
+
if (parent === dir) break;
|
|
143
|
+
dir = parent;
|
|
144
|
+
}
|
|
145
|
+
return process.cwd();
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Execute a local nexus subcommand via Bun.spawn().
|
|
150
|
+
*
|
|
151
|
+
* Output is streamed into the CommandRunnerStore for rendering by CommandOutput.
|
|
152
|
+
*/
|
|
153
|
+
export function executeLocalCommand(command: string, args: readonly string[]): void {
|
|
154
|
+
// Defense-in-depth: re-validate allowlist (already checked in parseCommand)
|
|
155
|
+
if (!ALLOWED_COMMANDS.has(command)) {
|
|
156
|
+
useCommandRunnerStore.getState().setSpawnError(
|
|
157
|
+
`Command "${command}" is not in the allowlist. Allowed: ${[...ALLOWED_COMMANDS].join(", ")}`,
|
|
158
|
+
);
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const store = useCommandRunnerStore.getState();
|
|
163
|
+
|
|
164
|
+
// Don't start a new command if one is already running
|
|
165
|
+
if (store.status === "running") {
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Resolve project root (git/worktree root) so commands like `nexus init`
|
|
170
|
+
// create nexus.yaml at the right level, not inside packages/nexus-tui/.
|
|
171
|
+
const projectRoot = findProjectRoot();
|
|
172
|
+
|
|
173
|
+
// Reset state
|
|
174
|
+
useCommandRunnerStore.setState({
|
|
175
|
+
...INITIAL_STATE,
|
|
176
|
+
status: "running",
|
|
177
|
+
commandLabel: `nexus ${command} ${args.join(" ")}`.trim(),
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
// Prefer .venv/bin/nexus (project venv) over system PATH to avoid picking up
|
|
181
|
+
// stale installs (e.g. /opt/anaconda3/bin/nexus which lacks the `up` command).
|
|
182
|
+
// Walk up from project root to find .venv/bin/nexus.
|
|
183
|
+
const path = require("node:path");
|
|
184
|
+
const nodeFs = require("node:fs");
|
|
185
|
+
let nexusBin = "nexus";
|
|
186
|
+
let searchDir = projectRoot;
|
|
187
|
+
for (let i = 0; i < 5; i++) {
|
|
188
|
+
const candidate = path.join(searchDir, ".venv", "bin", "nexus");
|
|
189
|
+
if (nodeFs.existsSync(candidate)) {
|
|
190
|
+
nexusBin = candidate;
|
|
191
|
+
break;
|
|
192
|
+
}
|
|
193
|
+
const parent = path.dirname(searchDir);
|
|
194
|
+
if (parent === searchDir) break;
|
|
195
|
+
searchDir = parent;
|
|
196
|
+
}
|
|
197
|
+
const fullArgs = [nexusBin, command, ...args];
|
|
198
|
+
|
|
199
|
+
// Read nexus.yaml from project root to pass NEXUS_URL and NEXUS_API_KEY
|
|
200
|
+
// to subcommands like `nexus demo init`.
|
|
201
|
+
const spawnEnv = { ...process.env };
|
|
202
|
+
try {
|
|
203
|
+
const fs = require("node:fs");
|
|
204
|
+
const yaml = fs.readFileSync(path.join(projectRoot, "nexus.yaml"), "utf-8") as string;
|
|
205
|
+
const portMatch = yaml.match(/ports:\s*\n(?:\s+\w+:[^\n]*\n)*?\s+http:\s*(\d+)/);
|
|
206
|
+
const keyMatch = yaml.match(/^api_key:\s*["']?([^"'\n]+)["']?/m);
|
|
207
|
+
if (portMatch?.[1] && !spawnEnv.NEXUS_URL) {
|
|
208
|
+
spawnEnv.NEXUS_URL = `http://localhost:${portMatch[1]}`;
|
|
209
|
+
}
|
|
210
|
+
if (keyMatch?.[1] && !spawnEnv.NEXUS_API_KEY) {
|
|
211
|
+
spawnEnv.NEXUS_API_KEY = keyMatch[1];
|
|
212
|
+
}
|
|
213
|
+
} catch {
|
|
214
|
+
// nexus.yaml not found yet (will be created by nexus init)
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
try {
|
|
218
|
+
// Commands run from project root so nexus.yaml is created at the right level
|
|
219
|
+
const proc = Bun.spawn(fullArgs, {
|
|
220
|
+
cwd: projectRoot,
|
|
221
|
+
stdout: "pipe",
|
|
222
|
+
stderr: "pipe",
|
|
223
|
+
env: spawnEnv,
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
activeProcesses.add(proc);
|
|
227
|
+
|
|
228
|
+
// Throttled output flushing (Decision 13C)
|
|
229
|
+
let pendingChunks = "";
|
|
230
|
+
let flushTimer: ReturnType<typeof setTimeout> | null = null;
|
|
231
|
+
|
|
232
|
+
function flushOutput(): void {
|
|
233
|
+
if (pendingChunks) {
|
|
234
|
+
useCommandRunnerStore.getState().appendOutput(pendingChunks);
|
|
235
|
+
pendingChunks = "";
|
|
236
|
+
}
|
|
237
|
+
flushTimer = null;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function bufferChunk(text: string): void {
|
|
241
|
+
pendingChunks += text;
|
|
242
|
+
if (!flushTimer) {
|
|
243
|
+
flushTimer = setTimeout(flushOutput, THROTTLE_MS);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Stream stdout
|
|
248
|
+
(async () => {
|
|
249
|
+
try {
|
|
250
|
+
const reader = proc.stdout.getReader();
|
|
251
|
+
const decoder = new TextDecoder();
|
|
252
|
+
while (true) {
|
|
253
|
+
const { done, value } = await reader.read();
|
|
254
|
+
if (done) break;
|
|
255
|
+
bufferChunk(decoder.decode(value, { stream: true }));
|
|
256
|
+
}
|
|
257
|
+
} catch {
|
|
258
|
+
// Stream closed
|
|
259
|
+
}
|
|
260
|
+
})();
|
|
261
|
+
|
|
262
|
+
// Stream stderr (interleaved with stdout)
|
|
263
|
+
(async () => {
|
|
264
|
+
try {
|
|
265
|
+
const reader = proc.stderr.getReader();
|
|
266
|
+
const decoder = new TextDecoder();
|
|
267
|
+
while (true) {
|
|
268
|
+
const { done, value } = await reader.read();
|
|
269
|
+
if (done) break;
|
|
270
|
+
bufferChunk(decoder.decode(value, { stream: true }));
|
|
271
|
+
}
|
|
272
|
+
} catch {
|
|
273
|
+
// Stream closed
|
|
274
|
+
}
|
|
275
|
+
})();
|
|
276
|
+
|
|
277
|
+
// Wait for process to complete
|
|
278
|
+
proc.exited.then((exitCode) => {
|
|
279
|
+
activeProcesses.delete(proc);
|
|
280
|
+
// Flush any remaining buffered output
|
|
281
|
+
if (flushTimer) {
|
|
282
|
+
clearTimeout(flushTimer);
|
|
283
|
+
}
|
|
284
|
+
flushOutput();
|
|
285
|
+
|
|
286
|
+
useCommandRunnerStore.getState().setStatus(
|
|
287
|
+
exitCode === 0 ? "success" : "error",
|
|
288
|
+
exitCode,
|
|
289
|
+
);
|
|
290
|
+
});
|
|
291
|
+
} catch (err) {
|
|
292
|
+
const message = err instanceof Error ? err.message : "Failed to spawn command";
|
|
293
|
+
|
|
294
|
+
// Common case: `nexus` binary not found
|
|
295
|
+
if (message.includes("ENOENT") || message.includes("not found")) {
|
|
296
|
+
useCommandRunnerStore.getState().setSpawnError(
|
|
297
|
+
`"nexus" command not found on PATH. Install the Nexus CLI: pip install nexus`,
|
|
298
|
+
);
|
|
299
|
+
} else {
|
|
300
|
+
useCommandRunnerStore.getState().setSpawnError(message);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { ConnectionStatus } from "../stores/global-store.js";
|
|
2
|
+
|
|
3
|
+
export type AnnouncementLevel = "info" | "success" | "error";
|
|
4
|
+
|
|
5
|
+
export function normalizeAnnouncementMessage(message: string): string {
|
|
6
|
+
return message.replace(/\s+/g, " ").trim();
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function formatPanelAnnouncement(label: string): string {
|
|
10
|
+
return normalizeAnnouncementMessage(`Panel ${label}`);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function formatConnectionAnnouncement(
|
|
14
|
+
status: ConnectionStatus,
|
|
15
|
+
error?: string | null,
|
|
16
|
+
): string {
|
|
17
|
+
switch (status) {
|
|
18
|
+
case "connected":
|
|
19
|
+
return "Connected";
|
|
20
|
+
case "connecting":
|
|
21
|
+
return "Connecting";
|
|
22
|
+
case "disconnected":
|
|
23
|
+
return "Disconnected";
|
|
24
|
+
case "error":
|
|
25
|
+
return normalizeAnnouncementMessage(`Connection error${error ? `: ${error}` : ""}`);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function formatDirectoryAnnouncement(path: string, count: number): string {
|
|
30
|
+
const noun = count === 1 ? "item" : "items";
|
|
31
|
+
return normalizeAnnouncementMessage(`${count} ${noun} in ${path}`);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function formatSelectionAnnouncement(name: string, isDirectory: boolean): string {
|
|
35
|
+
return normalizeAnnouncementMessage(`Selected ${isDirectory ? "folder" : "file"} ${name}`);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function formatErrorAnnouncement(message: string): string {
|
|
39
|
+
return normalizeAnnouncementMessage(`Error: ${message}`);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function formatSuccessAnnouncement(message: string): string {
|
|
43
|
+
return normalizeAnnouncementMessage(message);
|
|
44
|
+
}
|