@brimveyn/aimux 1.21.0 → 1.22.2
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 +27 -25
- package/package.json +2 -2
- package/src/app-runtime/auto-commit-driver.ts +22 -27
- package/src/app-runtime/backend-attach-runtime.ts +43 -36
- package/src/app-runtime/backend-runtime-events.ts +107 -88
- package/src/app-runtime/editor-actions.ts +201 -0
- package/src/app-runtime/git-actions.ts +197 -0
- package/src/app-runtime/measure-ref.ts +19 -0
- package/src/app-runtime/navigation-actions.ts +213 -0
- package/src/app-runtime/project-actions.ts +188 -0
- package/src/app-runtime/prompt-injection.ts +123 -0
- package/src/app-runtime/selection.ts +29 -0
- package/src/app-runtime/settings-actions.ts +156 -0
- package/src/app-runtime/setup-actions.ts +237 -0
- package/src/app-runtime/side-effect-context.ts +33 -0
- package/src/app-runtime/side-effects.ts +302 -1541
- package/src/app-runtime/snippet-actions.ts +3 -2
- package/src/app-runtime/tab-actions.ts +218 -0
- package/src/app-runtime/tab-runtime-timeouts.ts +1 -1
- package/src/app-runtime/use-auto-commit-driver.ts +14 -13
- package/src/app-runtime/use-backend-runtime.ts +12 -11
- package/src/app-runtime/use-directory-search.ts +5 -4
- package/src/app-runtime/use-mouse-handlers.ts +2 -1
- package/src/app-runtime/{use-workspace-autosave.ts → use-project-autosave.ts} +4 -4
- package/src/app-runtime/use-renderer-bindings.ts +2 -1
- package/src/app-runtime/use-setup-runner.ts +96 -0
- package/src/app-runtime/use-terminal-resize.ts +18 -14
- package/src/app-runtime/workspace-actions.ts +366 -0
- package/src/app-runtime/workspace-activity.ts +138 -0
- package/src/app-runtime/workspace-naming.ts +90 -0
- package/src/app.tsx +93 -48
- package/src/assets/sounds/bell.wav +0 -0
- package/src/assets/sounds/ding.wav +0 -0
- package/src/assets/sounds/plane.wav +0 -0
- package/src/cli/client/daemon-client.ts +2 -2
- package/src/cli/client/project-resolver.ts +112 -0
- package/src/cli/commands/project/close.ts +32 -0
- package/src/cli/commands/project/create.ts +93 -0
- package/src/cli/commands/project/list.ts +25 -0
- package/src/cli/commands/project/show.ts +32 -0
- package/src/cli/commands/{workspace → project}/switch.ts +19 -19
- package/src/cli/commands/tab/await.ts +2 -2
- package/src/cli/commands/tab/close.ts +3 -3
- package/src/cli/commands/tab/create.ts +74 -73
- package/src/cli/commands/tab/focus.ts +3 -3
- package/src/cli/commands/tab/list.ts +6 -6
- package/src/cli/commands/tab/run.ts +2 -2
- package/src/cli/commands/tab/send.ts +2 -2
- package/src/cli/commands/tab/snapshot.ts +2 -2
- package/src/cli/commands/tab/tail.ts +2 -2
- package/src/cli/commands/tab/wait.ts +2 -2
- package/src/cli/commands/worker/await.ts +3 -3
- package/src/cli/commands/worker/doctor.ts +30 -30
- package/src/cli/commands/worker/list.ts +15 -15
- package/src/cli/commands/worker/prompt.ts +3 -3
- package/src/cli/commands/worker/run.ts +23 -23
- package/src/cli/commands/worker/shared.ts +53 -59
- package/src/cli/commands/worker/stop.ts +29 -29
- package/src/cli/commands/worker/submit.ts +3 -3
- package/src/cli/commands/{worktree → workspace}/create-core.ts +26 -26
- package/src/cli/commands/workspace/create.ts +30 -68
- package/src/cli/commands/workspace/list.ts +52 -9
- package/src/cli/commands/workspace/remove.ts +81 -0
- package/src/cli/completion/plan.ts +2 -2
- package/src/cli/completion/sources.ts +18 -18
- package/src/cli/context.ts +13 -13
- package/src/cli/flags.ts +30 -10
- package/src/cli/index.ts +26 -16
- package/src/cli/output.ts +1 -1
- package/src/cli/registry.ts +12 -12
- package/src/config/loader.ts +16 -5
- package/src/config.ts +70 -112
- package/src/daemon/catalog-writer.ts +67 -67
- package/src/daemon/daemon.ts +205 -201
- package/src/daemon/session-manager.ts +39 -39
- package/src/daemon/session-registry.ts +14 -14
- package/src/git/divergence.ts +33 -2
- package/src/git/git-poller.ts +1 -1
- package/src/git/git-status.ts +2 -2
- package/src/git/{move-worktree.ts → move-workspace.ts} +5 -5
- package/src/git/pr-status.ts +1 -1
- package/src/git/repo-discovery.ts +1 -1
- package/src/git/use-repo-discovery.ts +1 -1
- package/src/git/workspace-branch-poller.ts +54 -0
- package/src/git/workspace-divergence-poller.ts +65 -0
- package/src/git/worktree.ts +29 -0
- package/src/index.tsx +4 -4
- package/src/input/keymap/help-entries.ts +7 -5
- package/src/input/modes/bridge.ts +13 -12
- package/src/input/modes/handlers/shared.ts +1 -1
- package/src/input/modes/transitions.ts +34 -21
- package/src/input/modes/types.ts +48 -31
- package/src/ipc/manager-protocol.ts +44 -44
- package/src/ipc/protocol.ts +192 -166
- package/src/platform/play-sound.ts +184 -0
- package/src/platform/project-search.ts +8 -8
- package/src/platform/worktree-paths.ts +8 -6
- package/src/pty/assistant-question-extractor.ts +1 -1
- package/src/pty/assistant-status-detection-loop.ts +79 -55
- package/src/pty/assistant-status-detector.ts +40 -15
- package/src/pty/command-registry.ts +81 -1
- package/src/restart-terminal-manager.ts +1 -1
- package/src/services/ai-usage/provider.ts +9 -2
- package/src/services/ai-usage/spawn.ts +19 -6
- package/src/session-backend/bootstrap.ts +4 -4
- package/src/session-backend/local-session-backend.ts +78 -75
- package/src/session-backend/remote-session-backend.ts +64 -52
- package/src/session-backend/types.ts +43 -32
- package/src/settings/live.ts +90 -0
- package/src/settings/search.ts +43 -0
- package/src/settings/sections/about.ts +49 -0
- package/src/settings/sections/appearance.ts +54 -0
- package/src/settings/sections/automation.ts +105 -0
- package/src/settings/sections/commands.ts +72 -0
- package/src/settings/sections/editor.ts +56 -0
- package/src/settings/sections/experimental.ts +57 -0
- package/src/settings/sections/git.ts +112 -0
- package/src/settings/sections/index.ts +95 -0
- package/src/settings/sections/integrations.ts +23 -0
- package/src/settings/sections/layout.ts +72 -0
- package/src/settings/sections/notifications.ts +99 -0
- package/src/settings/sections/setup.ts +54 -0
- package/src/settings/sections/status-bar.ts +63 -0
- package/src/settings/settings-store.ts +207 -0
- package/src/settings/types.ts +108 -0
- package/src/snippets/run-shell-var.ts +48 -22
- package/src/state/actions.ts +342 -0
- package/src/state/app-store.ts +2 -1
- package/src/state/bars.ts +1 -1
- package/src/state/dispatch-ref.ts +1 -1
- package/src/state/layout-resize.ts +2 -2
- package/src/state/project-catalog.ts +275 -0
- package/src/state/project-data.ts +143 -0
- package/src/state/{session-persistence.ts → project-persistence.ts} +74 -68
- package/src/state/project-save.ts +46 -0
- package/src/state/project-workspaces.ts +392 -0
- package/src/state/reducers/auto-commit-state.ts +11 -10
- package/src/state/reducers/git-commit-modal-state.ts +122 -0
- package/src/state/reducers/git-mode-state.ts +1 -1
- package/src/state/reducers/git-panel-state.ts +10 -6
- package/src/state/reducers/modal-state.ts +290 -681
- package/src/state/reducers/multi-repo-state.ts +3 -6
- package/src/state/reducers/project-state.ts +312 -0
- package/src/state/reducers/settings-state.ts +80 -0
- package/src/state/reducers/tab-state.ts +107 -60
- package/src/state/reducers/ui-state.ts +4 -3
- package/src/state/selectors.ts +49 -37
- package/src/state/store.ts +57 -24
- package/src/state/types.ts +244 -405
- package/src/state/validation.ts +14 -12
- package/src/terminal-manager/manager-client.ts +32 -32
- package/src/terminal-manager/terminal-manager.ts +24 -24
- package/src/ui/components/git/git-panel.tsx +2 -2
- package/src/ui/components/git/git-view.tsx +12 -12
- package/src/ui/components/git/image-diff/image-diff-view.tsx +1 -1
- package/src/ui/components/git/pane/git-pane-widget.tsx +7 -7
- package/src/ui/components/layout/sidebar/project-list.tsx +444 -0
- package/src/ui/components/layout/sidebar/tab-item.tsx +27 -12
- package/src/ui/components/layout/sidebar/use-sidebar-auto-scroll.ts +1 -1
- package/src/ui/components/layout/sidebar/workspace-row.tsx +253 -0
- package/src/ui/components/layout/status-bar.tsx +6 -2
- package/src/ui/components/layout/terminal-pane.tsx +34 -29
- package/src/ui/components/layout/top-tab-bar.tsx +46 -36
- package/src/ui/components/modals/git/git-commit-modal.tsx +8 -8
- package/src/ui/components/modals/{sessions/create-session-modal.tsx → projects/create-project-modal.tsx} +12 -12
- package/src/ui/components/modals/{sessions/session-name-modal.tsx → projects/project-name-modal.tsx} +2 -2
- package/src/ui/components/modals/{sessions/session-picker-modal.tsx → projects/project-picker-modal.tsx} +33 -35
- package/src/ui/components/modals/settings/settings-search-modal.tsx +70 -0
- package/src/ui/components/modals/shared/form.tsx +34 -22
- package/src/ui/components/modals/shared/picker.tsx +10 -10
- package/src/ui/components/modals/shared/{worktree-delete-confirm.tsx → workspace-delete-confirm.tsx} +9 -9
- package/src/ui/components/modals/tabs/new-tab-modal.tsx +30 -324
- package/src/ui/components/modals/workspace/create-workspace-modal.tsx +101 -0
- package/src/ui/components/modals/{worktree/worktree-move-confirm-modal.tsx → workspace/workspace-move-confirm-modal.tsx} +5 -5
- package/src/ui/components/modals/{worktree/worktree-move-modal.tsx → workspace/workspace-move-modal.tsx} +29 -27
- package/src/ui/components/primitives/input-field.tsx +7 -3
- package/src/ui/components/primitives/surface.tsx +3 -0
- package/src/ui/components/settings/row-value.tsx +74 -0
- package/src/ui/components/settings/settings-row.tsx +78 -0
- package/src/ui/components/settings/settings-view.tsx +169 -0
- package/src/ui/components/setup/setup-widget.tsx +152 -0
- package/src/ui/flash/build-labels.ts +27 -27
- package/src/ui/hooks/use-activity-sprite.ts +73 -0
- package/src/ui/hooks/use-scroll-active-into-view.ts +26 -0
- package/src/ui/{session-ordering.ts → project-ordering.ts} +4 -4
- package/src/ui/root.tsx +139 -121
- package/src/ui/status-bar-model.ts +48 -34
- package/src/ui/terminal-graphics/kitty.ts +28 -2
- package/src/ui/terminal-graphics/sprites/done@330/0.png +0 -0
- package/src/ui/terminal-graphics/sprites/done@330/1.png +0 -0
- package/src/ui/terminal-graphics/sprites/idle@1000/0.png +0 -0
- package/src/ui/terminal-graphics/sprites/idle@1000/1.png +0 -0
- package/src/ui/terminal-graphics/sprites/waiting@170/0.png +0 -0
- package/src/ui/terminal-graphics/sprites/waiting@170/1.png +0 -0
- package/src/ui/terminal-graphics/sprites/working@150/0.png +0 -0
- package/src/ui/terminal-graphics/sprites/working@150/1.png +0 -0
- package/src/ui/terminal-graphics/sprites/working@150/2.png +0 -0
- package/src/ui/terminal-graphics/sprites/working@150/3.png +0 -0
- package/src/ui/terminal-graphics/sprites.ts +246 -0
- package/src/ui/truncate.ts +7 -0
- package/src/ui/widgets/registry.tsx +6 -3
- package/src/ui/widgets/widget-context-menu.ts +26 -10
- package/src/update.ts +2 -2
- package/src/app-runtime/session-actions.ts +0 -187
- package/src/cli/client/workspace-resolver.ts +0 -110
- package/src/cli/commands/workspace/close.ts +0 -32
- package/src/cli/commands/workspace/show.ts +0 -32
- package/src/cli/commands/worktree/create.ts +0 -55
- package/src/cli/commands/worktree/list.ts +0 -68
- package/src/cli/commands/worktree/remove.ts +0 -81
- package/src/git/worktree-branch-poller.ts +0 -54
- package/src/git/worktree-divergence-poller.ts +0 -59
- package/src/state/reducers/session-state.ts +0 -262
- package/src/state/session-catalog.ts +0 -143
- package/src/state/session-worktrees.ts +0 -260
- package/src/state/workspace-save.ts +0 -43
- package/src/ui/components/layout/sidebar/use-top-tab-bar-auto-scroll.ts +0 -30
- package/src/ui/components/layout/sidebar/workspace-list.tsx +0 -418
- package/src/ui/components/layout/sidebar/worktree-row.tsx +0 -149
- /package/src/ui/{components/git/image-diff → terminal-graphics}/dimensions.ts +0 -0
package/src/app.tsx
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
|
+
type AimuxUserConfig,
|
|
2
3
|
type ResolvedConfig,
|
|
3
4
|
setAutoCommitEnabled,
|
|
4
5
|
setExternalEditorConfig,
|
|
@@ -8,20 +9,23 @@ import {
|
|
|
8
9
|
import { useKeyboard, useRenderer, useTerminalDimensions } from '@opentui/react'
|
|
9
10
|
import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react'
|
|
10
11
|
|
|
12
|
+
import type { SideEffectContext } from './app-runtime/side-effect-context'
|
|
11
13
|
import type { KeyChord } from './input/keymap/key-chord'
|
|
12
14
|
import type { TrieBinding } from './input/keymap/trie'
|
|
13
15
|
import type { KeyResult, ModeContext, ModeId } from './input/modes/types'
|
|
14
16
|
import type { TerminalContentOrigin } from './input/raw-input-handler'
|
|
15
17
|
import type { SessionBackend } from './session-backend/types'
|
|
16
18
|
|
|
17
|
-
import {
|
|
19
|
+
import { setActiveMeasure } from './app-runtime/measure-ref'
|
|
20
|
+
import { executeSideEffect } from './app-runtime/side-effects'
|
|
18
21
|
import { useAutoCommitDriver } from './app-runtime/use-auto-commit-driver'
|
|
19
22
|
import { useBackendRuntime } from './app-runtime/use-backend-runtime'
|
|
20
23
|
import { useDirectorySearch } from './app-runtime/use-directory-search'
|
|
21
24
|
import { useMouseHandlers } from './app-runtime/use-mouse-handlers'
|
|
25
|
+
import { useProjectAutosave } from './app-runtime/use-project-autosave'
|
|
22
26
|
import { useRendererBindings } from './app-runtime/use-renderer-bindings'
|
|
27
|
+
import { useSetupRunner } from './app-runtime/use-setup-runner'
|
|
23
28
|
import { useTerminalResize } from './app-runtime/use-terminal-resize'
|
|
24
|
-
import { useWorkspaceAutosave } from './app-runtime/use-workspace-autosave'
|
|
25
29
|
import { loadConfig, saveConfig } from './config'
|
|
26
30
|
import { enqueueGitOp } from './git/command-queue'
|
|
27
31
|
import { pruneOrphanAimuxBranches } from './git/worktree'
|
|
@@ -34,11 +38,19 @@ import { highlightSnapshot, warmClaudeSyntaxOverlay } from './integrations/claud
|
|
|
34
38
|
import { ensureClaudeSettingsThemePref, syncClaudeTheme } from './integrations/claude-theme-sync'
|
|
35
39
|
import { getProfileConfigDir, getProfileName } from './profile-paths'
|
|
36
40
|
import { startAIUsageService } from './services/ai-usage/provider'
|
|
41
|
+
import {
|
|
42
|
+
useAIUsageConfig,
|
|
43
|
+
useAutoCommitConfig,
|
|
44
|
+
useHarmonizeClaudeTheme,
|
|
45
|
+
useSnippetTriggerChar,
|
|
46
|
+
} from './settings/live'
|
|
47
|
+
import { ALL_SETTING_ROWS } from './settings/sections'
|
|
48
|
+
import { hydrateSettings } from './settings/settings-store'
|
|
37
49
|
import { aiUsageStore } from './state/ai-usage-store'
|
|
38
50
|
import { appStore, useAppStore } from './state/app-store'
|
|
39
51
|
import { setActiveDispatch, setActiveSideEffectRunner } from './state/dispatch-ref'
|
|
40
|
-
import {
|
|
41
|
-
import {
|
|
52
|
+
import { findMostRecentProject, loadProjectCatalog } from './state/project-catalog'
|
|
53
|
+
import { getActiveWorkspacePath } from './state/project-workspaces'
|
|
42
54
|
import { loadSnippetCatalog, mergeConfigSnippets } from './state/snippet-catalog'
|
|
43
55
|
import { createInitialState } from './state/store'
|
|
44
56
|
import { toast } from './state/toast-store'
|
|
@@ -59,14 +71,16 @@ import {
|
|
|
59
71
|
isNewerVersion,
|
|
60
72
|
} from './update/version-check'
|
|
61
73
|
|
|
62
|
-
const
|
|
74
|
+
const PROJECT_SAVE_DEBOUNCE_MS = 250
|
|
63
75
|
|
|
64
76
|
export function App({
|
|
65
77
|
backend,
|
|
66
78
|
resolvedConfig,
|
|
79
|
+
userConfig,
|
|
67
80
|
}: {
|
|
68
81
|
backend: SessionBackend
|
|
69
82
|
resolvedConfig: ResolvedConfig
|
|
83
|
+
userConfig: AimuxUserConfig
|
|
70
84
|
}) {
|
|
71
85
|
// Publish the auto-commit enabled flag before any children render so
|
|
72
86
|
// actions (which live outside React) can read it synchronously.
|
|
@@ -104,8 +118,8 @@ export function App({
|
|
|
104
118
|
// selector reads. We discard the value — the store is the source of truth.
|
|
105
119
|
useState(() => {
|
|
106
120
|
const json = loadConfig()
|
|
107
|
-
const
|
|
108
|
-
resolvedConfig.
|
|
121
|
+
const projectBarVisible =
|
|
122
|
+
resolvedConfig.projectBar?.initialVisible ?? json.projectBarVisible ?? true
|
|
109
123
|
// Merge config-file gitPane (persisted prefs) with user's resolved gitPane
|
|
110
124
|
// (programmatic config). User config wins; file provides persisted prior state.
|
|
111
125
|
const userGitPane = resolvedConfig.gitPane
|
|
@@ -118,32 +132,28 @@ export function App({
|
|
|
118
132
|
...(userGitPane?.diffCount !== undefined ? { diffCount: userGitPane.diffCount } : {}),
|
|
119
133
|
}
|
|
120
134
|
|
|
121
|
-
const
|
|
135
|
+
const projectCatalog = loadProjectCatalog()
|
|
122
136
|
const mergedSnippets = mergeConfigSnippets(loadSnippetCatalog(), resolvedConfig.snippets)
|
|
123
137
|
const initial = createInitialState(
|
|
124
138
|
json.customCommands,
|
|
125
|
-
|
|
139
|
+
projectCatalog,
|
|
126
140
|
mergedSnippets,
|
|
127
|
-
|
|
141
|
+
projectCatalog.length === 0,
|
|
128
142
|
{
|
|
129
143
|
bars: json.bars,
|
|
130
144
|
gitPane: gitPaneOverrides,
|
|
131
|
-
|
|
132
|
-
worktreeTemplates:
|
|
133
|
-
resolvedConfig.worktreeTemplates.length > 0
|
|
134
|
-
? resolvedConfig.worktreeTemplates
|
|
135
|
-
: json.worktreeTemplates,
|
|
145
|
+
projectBarVisible,
|
|
136
146
|
}
|
|
137
147
|
)
|
|
138
148
|
// Replace the module-level default with the fully-resolved initial state.
|
|
139
149
|
// Preserves the dispatch baked into the store by app-store.ts.
|
|
140
150
|
appStore.setState(initial)
|
|
141
|
-
const mostRecent =
|
|
151
|
+
const mostRecent = findMostRecentProject(projectCatalog)
|
|
142
152
|
if (mostRecent) {
|
|
143
153
|
appStore.getState().dispatch({
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
154
|
+
projectId: mostRecent.id,
|
|
155
|
+
projectSnapshot: mostRecent.projectSnapshot,
|
|
156
|
+
type: 'load-project',
|
|
147
157
|
})
|
|
148
158
|
}
|
|
149
159
|
return null
|
|
@@ -156,6 +166,15 @@ export function App({
|
|
|
156
166
|
const state = useAppStore((s) => s)
|
|
157
167
|
const dispatch = state.dispatch
|
|
158
168
|
|
|
169
|
+
// Config the settings screen can change under us. The resolved config file is
|
|
170
|
+
// the baseline each of these falls back to.
|
|
171
|
+
const autoCommitConfig = useAutoCommitConfig(resolvedConfig.autoCommit)
|
|
172
|
+
const aiUsageConfig = useAIUsageConfig(resolvedConfig.statusBar?.aiUsage)
|
|
173
|
+
const harmonizeClaudeTheme = useHarmonizeClaudeTheme(
|
|
174
|
+
resolvedConfig.theme?.beta?.harmonizeClaudeTheme
|
|
175
|
+
)
|
|
176
|
+
const snippetTriggerChar = useSnippetTriggerChar(resolvedConfig.snippetTriggerChar)
|
|
177
|
+
|
|
159
178
|
useLayoutEffect(() => {
|
|
160
179
|
setActiveDispatch(dispatch)
|
|
161
180
|
return () => {
|
|
@@ -164,14 +183,26 @@ export function App({
|
|
|
164
183
|
}
|
|
165
184
|
}, [dispatch])
|
|
166
185
|
|
|
186
|
+
// Replays what the settings screen has written on top of the config file (the
|
|
187
|
+
// setters at the top of this component are its baseline). Deliberately after
|
|
188
|
+
// the effect above and never during render: a row applies its value through
|
|
189
|
+
// whatever owns it, and some of those owners are reached by dispatching.
|
|
190
|
+
// Layout effect, so it lands before the first paint.
|
|
191
|
+
useLayoutEffect(() => {
|
|
192
|
+
hydrateSettings(ALL_SETTING_ROWS, userConfig)
|
|
193
|
+
// Once per launch: the config file is read at startup and does not change
|
|
194
|
+
// under us, and every later write goes through `writeRow`.
|
|
195
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
196
|
+
}, [])
|
|
197
|
+
|
|
167
198
|
useEffect(() => {
|
|
168
|
-
if (!
|
|
199
|
+
if (!harmonizeClaudeTheme) return
|
|
169
200
|
ensureClaudeSettingsThemePref()
|
|
170
201
|
syncClaudeTheme(getCurrentTheme(), getCurrentMode())
|
|
171
202
|
return subscribeThemeChanges((resolved, mode) => {
|
|
172
203
|
syncClaudeTheme(resolved, mode)
|
|
173
204
|
})
|
|
174
|
-
}, [
|
|
205
|
+
}, [harmonizeClaudeTheme])
|
|
175
206
|
|
|
176
207
|
useEffect(() => {
|
|
177
208
|
// Opt-in via `integrations.claudeHooks` in aimux.config.ts. When enabled,
|
|
@@ -184,13 +215,12 @@ export function App({
|
|
|
184
215
|
}, [resolvedConfig.integrations.claudeHooks])
|
|
185
216
|
|
|
186
217
|
useEffect(() => {
|
|
187
|
-
|
|
188
|
-
if (!(aiUsage?.enabled === true)) {
|
|
218
|
+
if (!(aiUsageConfig.enabled === true)) {
|
|
189
219
|
aiUsageStore.getState().setEnabled(false)
|
|
190
220
|
return
|
|
191
221
|
}
|
|
192
222
|
aiUsageStore.getState().setEnabled(true)
|
|
193
|
-
const handle = startAIUsageService(
|
|
223
|
+
const handle = startAIUsageService(aiUsageConfig, (snap) => {
|
|
194
224
|
aiUsageStore.getState().setSnapshot(snap)
|
|
195
225
|
})
|
|
196
226
|
return () => {
|
|
@@ -198,7 +228,9 @@ export function App({
|
|
|
198
228
|
aiUsageStore.getState().clear()
|
|
199
229
|
aiUsageStore.getState().setEnabled(false)
|
|
200
230
|
}
|
|
201
|
-
|
|
231
|
+
// Memoized by `useAIUsageConfig`, so toggling the indicator restarts the
|
|
232
|
+
// service and nothing else does.
|
|
233
|
+
}, [aiUsageConfig])
|
|
202
234
|
|
|
203
235
|
useEffect(() => {
|
|
204
236
|
if (process.env.AIMUX_DISABLE_UPDATE_CHECK === '1') return
|
|
@@ -228,16 +260,16 @@ export function App({
|
|
|
228
260
|
}, [])
|
|
229
261
|
|
|
230
262
|
useEffect(() => {
|
|
231
|
-
// One-shot cleanup: prune `aimux/` branches orphaned by temp
|
|
263
|
+
// One-shot cleanup: prune `aimux/` branches orphaned by temp workspaces that
|
|
232
264
|
// were deleted before delete-time branch cleanup existed. Gated by a config
|
|
233
265
|
// flag so it runs once per machine. All git calls are best-effort (nothrow)
|
|
234
|
-
// and git protects branches still checked out in a live
|
|
266
|
+
// and git protects branches still checked out in a live workspace.
|
|
235
267
|
if (loadConfig().prunedOrphanAimuxBranches === true) return
|
|
236
268
|
void (async () => {
|
|
237
269
|
const repoRoots = new Set<string>()
|
|
238
|
-
for (const
|
|
239
|
-
for (const
|
|
240
|
-
if (
|
|
270
|
+
for (const project of loadProjectCatalog()) {
|
|
271
|
+
for (const workspace of project.workspaces ?? []) {
|
|
272
|
+
if (workspace.repoRoot !== '') repoRoots.add(workspace.repoRoot)
|
|
241
273
|
}
|
|
242
274
|
}
|
|
243
275
|
let removed = 0
|
|
@@ -256,15 +288,15 @@ export function App({
|
|
|
256
288
|
// still the bootstrap default, and useBackendRuntime's attach effect would
|
|
257
289
|
// then hand 80x24 to the backend, resizing the daemon-persisted PTYs (or
|
|
258
290
|
// newly-spawned ones) to the wrong size and stranding the assistant's
|
|
259
|
-
// already-drawn content in the top-left until a
|
|
291
|
+
// already-drawn content in the top-left until a project switch.
|
|
260
292
|
const layoutRef = useRef(state.layout)
|
|
261
293
|
const activeTab = useMemo(
|
|
262
294
|
() => state.tabs.find((tab) => tab.id === state.activeTabId),
|
|
263
295
|
[state.activeTabId, state.tabs]
|
|
264
296
|
)
|
|
265
|
-
const
|
|
266
|
-
() => state.
|
|
267
|
-
[state.
|
|
297
|
+
const currentProject = useMemo(
|
|
298
|
+
() => state.projects.find((project) => project.id === state.currentProjectId),
|
|
299
|
+
[state.currentProjectId, state.projects]
|
|
268
300
|
)
|
|
269
301
|
const activeMouseForwardingEnabled = activeTab?.terminalModes.mouseTrackingMode !== 'none'
|
|
270
302
|
const activeLocalScrollbackEnabled =
|
|
@@ -285,11 +317,11 @@ export function App({
|
|
|
285
317
|
snippetsRef.current = state.snippets
|
|
286
318
|
const branchRef = useRef(state.gitPanel.branch)
|
|
287
319
|
branchRef.current = state.gitPanel.branch
|
|
288
|
-
const triggerCharRef = useRef(
|
|
289
|
-
triggerCharRef.current =
|
|
320
|
+
const triggerCharRef = useRef(snippetTriggerChar)
|
|
321
|
+
triggerCharRef.current = snippetTriggerChar
|
|
290
322
|
|
|
291
323
|
const contentOriginRef = useRef<TerminalContentOrigin>({ cols: 0, rows: 0, x: 0, y: 0 })
|
|
292
|
-
const
|
|
324
|
+
const currentProjectProjectSnapshot = currentProject?.projectSnapshot
|
|
293
325
|
|
|
294
326
|
// Must run before useBackendRuntime so its open-loop terminalSize seeds
|
|
295
327
|
// layoutRef before the attach effect reads it. See the comment on layoutRef.
|
|
@@ -303,6 +335,15 @@ export function App({
|
|
|
303
335
|
})
|
|
304
336
|
layoutRef.current = { terminalCols: terminalSize.cols, terminalRows: terminalSize.rows }
|
|
305
337
|
|
|
338
|
+
// Reaches the setup widget, which hosts its own PTY outside root.tsx's pane
|
|
339
|
+
// tree and so never sees the onMeasure prop chain.
|
|
340
|
+
useLayoutEffect(() => {
|
|
341
|
+
setActiveMeasure(terminalSize.onMeasure)
|
|
342
|
+
return () => {
|
|
343
|
+
setActiveMeasure(null)
|
|
344
|
+
}
|
|
345
|
+
}, [terminalSize.onMeasure])
|
|
346
|
+
|
|
306
347
|
const syntaxOverlayFlag = resolvedConfig.theme?.beta?.experimentalSyntaxHighlight === true
|
|
307
348
|
const syntaxOverlayFlagRef = useRef(syntaxOverlayFlag)
|
|
308
349
|
syntaxOverlayFlagRef.current = syntaxOverlayFlag
|
|
@@ -337,18 +378,20 @@ export function App({
|
|
|
337
378
|
const { clearIdleTimer, clearStartupGrace, startStartupGrace } = useBackendRuntime({
|
|
338
379
|
activeTabId: state.activeTabId,
|
|
339
380
|
backend,
|
|
340
|
-
|
|
341
|
-
|
|
381
|
+
currentProjectId: state.currentProjectId,
|
|
382
|
+
currentProjectProjectSnapshot,
|
|
342
383
|
dispatch,
|
|
343
384
|
layoutRef,
|
|
344
385
|
resizingRef,
|
|
345
386
|
syntaxOverlayEnabled,
|
|
346
387
|
})
|
|
347
388
|
|
|
348
|
-
|
|
389
|
+
useProjectAutosave(state, PROJECT_SAVE_DEBOUNCE_MS)
|
|
349
390
|
useDirectorySearch(state.modal, dispatch)
|
|
350
391
|
useAutoCommitDriver({
|
|
351
|
-
|
|
392
|
+
// Through the settings screen, so a change lands without a restart. The
|
|
393
|
+
// resolved config is the baseline it falls back to.
|
|
394
|
+
config: autoCommitConfig,
|
|
352
395
|
dispatch,
|
|
353
396
|
getProfileConfigRoot: getProfileConfigDir,
|
|
354
397
|
state,
|
|
@@ -416,9 +459,9 @@ export function App({
|
|
|
416
459
|
clearIdleTimer,
|
|
417
460
|
clearStartupGrace,
|
|
418
461
|
dispatch,
|
|
419
|
-
|
|
420
|
-
if (!(state.
|
|
421
|
-
return
|
|
462
|
+
getCurrentProjectProjectPath: () => {
|
|
463
|
+
if (!(state.currentProjectId != null && state.currentProjectId !== '')) return
|
|
464
|
+
return getActiveWorkspacePath(state.projects.find((s) => s.id === state.currentProjectId))
|
|
422
465
|
},
|
|
423
466
|
// Read straight from the store, not stateRef. stateRef is only refreshed
|
|
424
467
|
// on render, so within a single JS turn (a click handler that dispatches
|
|
@@ -432,6 +475,8 @@ export function App({
|
|
|
432
475
|
themeId,
|
|
433
476
|
}
|
|
434
477
|
|
|
478
|
+
useSetupRunner(state, sideEffectCtx)
|
|
479
|
+
|
|
435
480
|
function processKeyResult(result: KeyResult, modeId: ModeId): void {
|
|
436
481
|
for (const action of result.actions) {
|
|
437
482
|
dispatch(action)
|
|
@@ -488,12 +533,12 @@ export function App({
|
|
|
488
533
|
dispatch({ tabId: target.tabId, type: 'set-active-tab' })
|
|
489
534
|
return
|
|
490
535
|
}
|
|
491
|
-
if (target.kind === '
|
|
536
|
+
if (target.kind === 'project' || target.kind === 'workspace') {
|
|
492
537
|
executeSideEffect(
|
|
493
538
|
{
|
|
494
|
-
index: target.
|
|
495
|
-
type: 'switch-
|
|
496
|
-
|
|
539
|
+
index: target.projectIndex,
|
|
540
|
+
type: 'switch-project-by-index',
|
|
541
|
+
workspaceId: target.workspaceId,
|
|
497
542
|
},
|
|
498
543
|
sideEffectCtx
|
|
499
544
|
)
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -205,10 +205,10 @@ export class DaemonClient {
|
|
|
205
205
|
return response.payload
|
|
206
206
|
}
|
|
207
207
|
|
|
208
|
-
async listTabs(
|
|
208
|
+
async listTabs(projectId: string): Promise<ListTabsResult> {
|
|
209
209
|
const response = await this.send({
|
|
210
210
|
id: crypto.randomUUID(),
|
|
211
|
-
payload: {
|
|
211
|
+
payload: { projectId },
|
|
212
212
|
type: 'listTabs',
|
|
213
213
|
})
|
|
214
214
|
if (response.type !== 'listTabsResult') {
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import type { ProjectRecord, WorkspaceRecord } from '../../state/types'
|
|
2
|
+
|
|
3
|
+
import { findMostRecentProject, loadProjectCatalog } from '../../state/project-catalog'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Where a command's target project came from. An orchestrator needs this to
|
|
7
|
+
* tell "I asked for pragma-once" from "aimux guessed, and the UI had moved on":
|
|
8
|
+
* `active` is the only origin that can silently follow the UI to another repo.
|
|
9
|
+
*/
|
|
10
|
+
export type ProjectOrigin = 'flag' | 'env' | 'active'
|
|
11
|
+
|
|
12
|
+
export interface ResolvedProject {
|
|
13
|
+
origin: ProjectOrigin
|
|
14
|
+
record: ProjectRecord
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Env pin for headless orchestrators — `--project` still wins over it. */
|
|
18
|
+
export const PROJECT_ENV_VAR = 'AIMUX_PROJECT'
|
|
19
|
+
/** ponytail: pre-rename spelling, honoured for one release. Drop with --workspace. */
|
|
20
|
+
const LEGACY_PROJECT_ENV_VAR = 'AIMUX_WORKSPACE'
|
|
21
|
+
|
|
22
|
+
/** The project's primary (root) workspace, i.e. the repository it is about. */
|
|
23
|
+
export function findPrimaryWorkspace(project: ProjectRecord): WorkspaceRecord | undefined {
|
|
24
|
+
return project.workspaces?.find((workspace) => workspace.source === 'primary')
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* The repo every fresh workspace for this project is cut from. Surfaced in
|
|
29
|
+
* every worker envelope so an agent can see *which project* it just acted on
|
|
30
|
+
* instead of inferring it from a workspace path hash.
|
|
31
|
+
*/
|
|
32
|
+
export function projectRepoRoot(project: ProjectRecord): string | null {
|
|
33
|
+
return findPrimaryWorkspace(project)?.repoRoot ?? project.projectPath ?? null
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Stable project identity block embedded in command output. */
|
|
37
|
+
export function projectIdentity(project: ProjectRecord): {
|
|
38
|
+
id: string
|
|
39
|
+
name: string
|
|
40
|
+
repoRoot: string | null
|
|
41
|
+
} {
|
|
42
|
+
return { id: project.id, name: project.name, repoRoot: projectRepoRoot(project) }
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Resolve `--project W` to a project record from the catalog, reporting where
|
|
47
|
+
* the choice came from. Precedence: the explicit flag, then `AIMUX_PROJECT`,
|
|
48
|
+
* then the most recently opened project. Throws when the catalog is empty (no
|
|
49
|
+
* project has ever been created) or when the explicit name/id doesn't match.
|
|
50
|
+
*
|
|
51
|
+
* Matching: exact id wins; otherwise exact name (case-sensitive); otherwise
|
|
52
|
+
* unique case-insensitive name match.
|
|
53
|
+
*/
|
|
54
|
+
export function resolveProjectWithOrigin(name: string | undefined): ResolvedProject {
|
|
55
|
+
const projects = loadProjectCatalog()
|
|
56
|
+
if (projects.length === 0) {
|
|
57
|
+
throw new Error(
|
|
58
|
+
'no projects found — create one from the aimux UI first (or pass --project once created)'
|
|
59
|
+
)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const flag = name !== undefined && name !== '' ? name : undefined
|
|
63
|
+
const env = process.env[PROJECT_ENV_VAR] ?? process.env[LEGACY_PROJECT_ENV_VAR]
|
|
64
|
+
const fromEnv = env != null && env !== '' ? env : undefined
|
|
65
|
+
const selector = flag ?? fromEnv
|
|
66
|
+
if (selector === undefined) {
|
|
67
|
+
const active = findMostRecentProject(projects)
|
|
68
|
+
if (!active) {
|
|
69
|
+
throw new Error('no active project and the catalog is empty')
|
|
70
|
+
}
|
|
71
|
+
return { origin: 'active', record: active }
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
origin: flag !== undefined ? 'flag' : 'env',
|
|
76
|
+
record: matchProject(projects, selector),
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function resolveProject(name: string | undefined): ProjectRecord {
|
|
81
|
+
return resolveProjectWithOrigin(name).record
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function matchProject(projects: ProjectRecord[], name: string): ProjectRecord {
|
|
85
|
+
const byId = projects.find((project) => project.id === name)
|
|
86
|
+
if (byId) return byId
|
|
87
|
+
|
|
88
|
+
const exactNameMatches = projects.filter((project) => project.name === name)
|
|
89
|
+
if (exactNameMatches.length > 1) {
|
|
90
|
+
throw new Error(
|
|
91
|
+
`project "${name}" matches multiple projects: ${exactNameMatches.map((s) => s.id).join(', ')}`
|
|
92
|
+
)
|
|
93
|
+
}
|
|
94
|
+
const exactOnly = exactNameMatches[0]
|
|
95
|
+
if (exactOnly) return exactOnly
|
|
96
|
+
|
|
97
|
+
const lower = name.toLowerCase()
|
|
98
|
+
const ciMatches = projects.filter((project) => project.name.toLowerCase() === lower)
|
|
99
|
+
if (ciMatches.length > 1) {
|
|
100
|
+
throw new Error(
|
|
101
|
+
`project "${name}" matches multiple projects: ${ciMatches.map((s) => s.id).join(', ')}`
|
|
102
|
+
)
|
|
103
|
+
}
|
|
104
|
+
const ciOnly = ciMatches[0]
|
|
105
|
+
if (ciOnly) return ciOnly
|
|
106
|
+
|
|
107
|
+
throw new Error(`project not found: ${name}`)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function listProjects(): ProjectRecord[] {
|
|
111
|
+
return loadProjectCatalog()
|
|
112
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { CliCommand } from '../../registry'
|
|
2
|
+
|
|
3
|
+
import { IPC_CAPABILITY_PROJECT_LIFECYCLE } from '../../../ipc/protocol'
|
|
4
|
+
import { resolveProject } from '../../client/project-resolver'
|
|
5
|
+
import { SHARED_FLAGS } from '../../flags'
|
|
6
|
+
import { EXIT_OK, writeJson } from '../../output'
|
|
7
|
+
|
|
8
|
+
export const projectClose: CliCommand = {
|
|
9
|
+
args: [{ complete: { kind: 'dynamic', source: 'project' }, name: 'project', required: true }],
|
|
10
|
+
flags: SHARED_FLAGS,
|
|
11
|
+
group: 'project',
|
|
12
|
+
run: async (ctx) => {
|
|
13
|
+
const target = ctx.args.positionals[0]
|
|
14
|
+
if (typeof target !== 'string' || target.length === 0) {
|
|
15
|
+
throw new Error('target project is required (id or name)')
|
|
16
|
+
}
|
|
17
|
+
const project = resolveProject(target)
|
|
18
|
+
|
|
19
|
+
const daemon = await ctx.getDaemon()
|
|
20
|
+
if (!daemon.hasCapability(IPC_CAPABILITY_PROJECT_LIFECYCLE)) {
|
|
21
|
+
throw new Error(
|
|
22
|
+
'daemon predates projectLifecycle capability — restart aimux to pick up the new daemon'
|
|
23
|
+
)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
await daemon.expectOk('closeProject', { targetProjectId: project.id })
|
|
27
|
+
writeJson({ closedProjectId: project.id, name: project.name })
|
|
28
|
+
return EXIT_OK
|
|
29
|
+
},
|
|
30
|
+
summary: 'Close a project (via the UI when attached, otherwise the catalog)',
|
|
31
|
+
verb: 'close',
|
|
32
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { resolve as resolvePath } from 'node:path'
|
|
2
|
+
|
|
3
|
+
import type { CliCommand } from '../../registry'
|
|
4
|
+
|
|
5
|
+
import { IPC_CAPABILITY_PROJECT_LIFECYCLE } from '../../../ipc/protocol'
|
|
6
|
+
import { SHARED_FLAGS } from '../../flags'
|
|
7
|
+
import { EXIT_OK, EXIT_TIMEOUT, writeJson } from '../../output'
|
|
8
|
+
|
|
9
|
+
const DEFAULT_WAIT_TIMEOUT_MS = 30_000
|
|
10
|
+
|
|
11
|
+
export const projectCreate: CliCommand = {
|
|
12
|
+
args: [{ complete: { kind: 'none' }, name: 'name', required: true }],
|
|
13
|
+
flags: [
|
|
14
|
+
...SHARED_FLAGS,
|
|
15
|
+
{
|
|
16
|
+
complete: { kind: 'file' },
|
|
17
|
+
description: 'project path to associate with the project',
|
|
18
|
+
kind: 'string',
|
|
19
|
+
name: 'project',
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
description: 'immediately switch the running UI to the new project',
|
|
23
|
+
kind: 'boolean',
|
|
24
|
+
name: 'switch',
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
description: 'with --switch, wait until the UI confirms the switch completed',
|
|
28
|
+
kind: 'boolean',
|
|
29
|
+
name: 'wait',
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
description: 'timeout for --wait, in milliseconds (default 30000)',
|
|
33
|
+
kind: 'number',
|
|
34
|
+
name: 'timeout',
|
|
35
|
+
},
|
|
36
|
+
],
|
|
37
|
+
group: 'project',
|
|
38
|
+
run: async (ctx) => {
|
|
39
|
+
const name = ctx.args.positionals[0]
|
|
40
|
+
if (typeof name !== 'string' || name.length === 0) {
|
|
41
|
+
throw new Error('project name is required')
|
|
42
|
+
}
|
|
43
|
+
const projectRaw =
|
|
44
|
+
typeof ctx.args.flags.project === 'string' ? ctx.args.flags.project : undefined
|
|
45
|
+
const projectPath = projectRaw === undefined ? undefined : resolvePath(projectRaw)
|
|
46
|
+
const doSwitch = ctx.args.flags.switch === true
|
|
47
|
+
const wait = ctx.args.flags.wait === true
|
|
48
|
+
const timeoutMs =
|
|
49
|
+
typeof ctx.args.flags.timeout === 'number' ? ctx.args.flags.timeout : DEFAULT_WAIT_TIMEOUT_MS
|
|
50
|
+
|
|
51
|
+
if (wait && !doSwitch) {
|
|
52
|
+
// Without --switch, the UI never emits an ack event, so there's
|
|
53
|
+
// nothing meaningful for --wait to wait on.
|
|
54
|
+
throw new Error('--wait requires --switch (nothing to wait for otherwise)')
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const daemon = await ctx.getDaemon()
|
|
58
|
+
if (!daemon.hasCapability(IPC_CAPABILITY_PROJECT_LIFECYCLE)) {
|
|
59
|
+
throw new Error(
|
|
60
|
+
'daemon predates projectLifecycle capability — restart aimux to pick up the new daemon'
|
|
61
|
+
)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (!wait) {
|
|
65
|
+
await daemon.expectOk('createProject', { name, projectPath, switch: doSwitch })
|
|
66
|
+
writeJson({ name, projectPath, switch: doSwitch })
|
|
67
|
+
return EXIT_OK
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Subscribe BEFORE sending the request. The UI's create+switch path
|
|
71
|
+
// relays as `projectSwitched` once handleCreateProjectEffect has
|
|
72
|
+
// dispatched load-project. Match on name+projectPath since the CLI
|
|
73
|
+
// doesn't know the id the UI will assign to the new project.
|
|
74
|
+
const settled = new Promise<number>((resolve) => {
|
|
75
|
+
const off = daemon.on('projectSwitched', (payload) => {
|
|
76
|
+
off()
|
|
77
|
+
clearTimeout(timer)
|
|
78
|
+
writeJson({ name, projectId: payload.projectId, projectPath, switch: doSwitch })
|
|
79
|
+
resolve(EXIT_OK)
|
|
80
|
+
})
|
|
81
|
+
const timer = setTimeout(() => {
|
|
82
|
+
off()
|
|
83
|
+
writeJson({ error: 'timed out waiting for projectSwitched', name, projectPath })
|
|
84
|
+
resolve(EXIT_TIMEOUT)
|
|
85
|
+
}, timeoutMs)
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
await daemon.expectOk('createProject', { name, projectPath, switch: doSwitch })
|
|
89
|
+
return settled
|
|
90
|
+
},
|
|
91
|
+
summary: 'Create a new project (via the UI when attached, otherwise the catalog)',
|
|
92
|
+
verb: 'create',
|
|
93
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { CliCommand } from '../../registry'
|
|
2
|
+
|
|
3
|
+
import { listProjects } from '../../client/project-resolver'
|
|
4
|
+
import { SHARED_FLAGS } from '../../flags'
|
|
5
|
+
import { EXIT_OK, writeJson } from '../../output'
|
|
6
|
+
|
|
7
|
+
export const projectList: CliCommand = {
|
|
8
|
+
args: [],
|
|
9
|
+
flags: SHARED_FLAGS,
|
|
10
|
+
group: 'project',
|
|
11
|
+
run: async () => {
|
|
12
|
+
const projects = listProjects()
|
|
13
|
+
writeJson({
|
|
14
|
+
projects: projects.map((project) => ({
|
|
15
|
+
id: project.id,
|
|
16
|
+
lastOpenedAt: project.lastOpenedAt,
|
|
17
|
+
name: project.name,
|
|
18
|
+
projectPath: project.projectPath,
|
|
19
|
+
})),
|
|
20
|
+
})
|
|
21
|
+
return EXIT_OK
|
|
22
|
+
},
|
|
23
|
+
summary: 'List known projects in the profile catalog',
|
|
24
|
+
verb: 'list',
|
|
25
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { CliCommand } from '../../registry'
|
|
2
|
+
|
|
3
|
+
import { SHARED_FLAGS } from '../../flags'
|
|
4
|
+
import { EXIT_OK, writeJson } from '../../output'
|
|
5
|
+
|
|
6
|
+
export const projectShow: CliCommand = {
|
|
7
|
+
args: [],
|
|
8
|
+
flags: SHARED_FLAGS,
|
|
9
|
+
group: 'project',
|
|
10
|
+
run: async (ctx) => {
|
|
11
|
+
const project = ctx.getProject()
|
|
12
|
+
writeJson({
|
|
13
|
+
activeWorkspaceId: project.activeWorkspaceId,
|
|
14
|
+
createdAt: project.createdAt,
|
|
15
|
+
id: project.id,
|
|
16
|
+
lastOpenedAt: project.lastOpenedAt,
|
|
17
|
+
name: project.name,
|
|
18
|
+
projectPath: project.projectPath,
|
|
19
|
+
workspaces:
|
|
20
|
+
project.workspaces?.map((w) => ({
|
|
21
|
+
branch: w.branch,
|
|
22
|
+
id: w.id,
|
|
23
|
+
name: w.name,
|
|
24
|
+
path: w.path,
|
|
25
|
+
source: w.source,
|
|
26
|
+
})) ?? [],
|
|
27
|
+
})
|
|
28
|
+
return EXIT_OK
|
|
29
|
+
},
|
|
30
|
+
summary: 'Show the active project (or the one named via --project)',
|
|
31
|
+
verb: 'show',
|
|
32
|
+
}
|