@membranehq/cli 0.1.1 → 0.1.3
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/dist/index.js +140 -140
- package/package.json +16 -4
- package/.turbo/turbo-build.log +0 -9
- package/CHANGELOG.md +0 -7
- package/scripts/add-shebang.sh +0 -6
- package/scripts/prepare-package-json.ts +0 -29
- package/src/agent.tsx +0 -50
- package/src/cli.ts +0 -72
- package/src/commands/open.command.ts +0 -51
- package/src/commands/pull.command.ts +0 -75
- package/src/commands/push.command.ts +0 -79
- package/src/commands/test.command.ts +0 -99
- package/src/components/AddMcpServerScreen.tsx +0 -215
- package/src/components/AgentStatus.tsx +0 -15
- package/src/components/Main.tsx +0 -64
- package/src/components/OverviewSection.tsx +0 -24
- package/src/components/PersonalAccessTokenInput.tsx +0 -56
- package/src/components/RecentChanges.tsx +0 -65
- package/src/components/SelectWorkspace.tsx +0 -112
- package/src/components/Setup.tsx +0 -121
- package/src/components/WorkspaceStatus.tsx +0 -61
- package/src/contexts/FileWatcherContext.tsx +0 -81
- package/src/index.ts +0 -27
- package/src/legacy/commands/pullWorkspace.ts +0 -70
- package/src/legacy/commands/pushWorkspace.ts +0 -246
- package/src/legacy/integrationElements.ts +0 -78
- package/src/legacy/push/types.ts +0 -17
- package/src/legacy/reader/index.ts +0 -113
- package/src/legacy/types.ts +0 -17
- package/src/legacy/util.ts +0 -149
- package/src/legacy/workspace-elements/connectors.ts +0 -397
- package/src/legacy/workspace-elements/index.ts +0 -27
- package/src/legacy/workspace-tools/commands/pullWorkspace.ts +0 -70
- package/src/legacy/workspace-tools/integrationElements.ts +0 -78
- package/src/legacy/workspace-tools/util.ts +0 -149
- package/src/mcp/server-status.ts +0 -27
- package/src/mcp/server.ts +0 -36
- package/src/mcp/tools/getTestAccessToken.ts +0 -32
- package/src/modules/api/account-api-client.ts +0 -89
- package/src/modules/api/index.ts +0 -3
- package/src/modules/api/membrane-api-client.ts +0 -116
- package/src/modules/api/workspace-api-client.ts +0 -11
- package/src/modules/config/cwd-context.tsx +0 -11
- package/src/modules/config/project/getAgentVersion.ts +0 -16
- package/src/modules/config/project/index.ts +0 -8
- package/src/modules/config/project/paths.ts +0 -25
- package/src/modules/config/project/readProjectConfig.ts +0 -27
- package/src/modules/config/project/useProjectConfig.tsx +0 -103
- package/src/modules/config/system/index.ts +0 -35
- package/src/modules/file-watcher/index.ts +0 -166
- package/src/modules/file-watcher/types.ts +0 -14
- package/src/modules/setup/steps.ts +0 -9
- package/src/modules/setup/useSetup.ts +0 -16
- package/src/modules/status/useStatus.ts +0 -16
- package/src/modules/workspace-element-service/constants.ts +0 -121
- package/src/modules/workspace-element-service/getTypeAndKeyFromPath.ts +0 -69
- package/src/modules/workspace-element-service/index.ts +0 -304
- package/src/testing/environment.ts +0 -172
- package/src/testing/runners/base.runner.ts +0 -27
- package/src/testing/runners/test.runner.ts +0 -123
- package/src/testing/scripts/generate-test-report.ts +0 -757
- package/src/testing/test-suites/base.ts +0 -92
- package/src/testing/test-suites/data-collection.ts +0 -128
- package/src/testing/testers/base.ts +0 -115
- package/src/testing/testers/create.ts +0 -273
- package/src/testing/testers/delete.ts +0 -155
- package/src/testing/testers/find-by-id.ts +0 -135
- package/src/testing/testers/list.ts +0 -110
- package/src/testing/testers/match.ts +0 -149
- package/src/testing/testers/search.ts +0 -148
- package/src/testing/testers/spec.ts +0 -30
- package/src/testing/testers/update.ts +0 -284
- package/src/utils/auth.ts +0 -19
- package/src/utils/constants.ts +0 -27
- package/src/utils/fields.ts +0 -83
- package/src/utils/logger.ts +0 -106
- package/src/utils/templating.ts +0 -50
- package/tsconfig.json +0 -21
|
@@ -1,215 +0,0 @@
|
|
|
1
|
-
import fs from 'node:fs'
|
|
2
|
-
import path from 'node:path'
|
|
3
|
-
|
|
4
|
-
import { useState, useEffect } from 'react'
|
|
5
|
-
import { Box, Text, useApp, useInput } from 'ink'
|
|
6
|
-
import SelectInput from 'ink-select-input'
|
|
7
|
-
|
|
8
|
-
import { useCwd } from '../modules/config/cwd-context'
|
|
9
|
-
|
|
10
|
-
const AGENTS = [
|
|
11
|
-
{ label: 'Cursor', value: 'cursor' },
|
|
12
|
-
{ label: 'Claude Code', value: 'claude' },
|
|
13
|
-
{ label: 'Windsurf', value: 'windsurf' },
|
|
14
|
-
]
|
|
15
|
-
|
|
16
|
-
const MCP_URL = 'http://localhost:8001/mcp'
|
|
17
|
-
|
|
18
|
-
function getSummary(agent: string): string {
|
|
19
|
-
switch (agent) {
|
|
20
|
-
case 'cursor':
|
|
21
|
-
return 'Add MCP server to .cursor/mcp.json'
|
|
22
|
-
case 'claude':
|
|
23
|
-
return 'Add MCP server to .claude/mcp.json'
|
|
24
|
-
case 'windsurf':
|
|
25
|
-
return 'Add MCP server to windsurf.config.json'
|
|
26
|
-
default:
|
|
27
|
-
return ''
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
function getConfigPath(agent: string, cwd: string): string {
|
|
32
|
-
switch (agent) {
|
|
33
|
-
case 'cursor':
|
|
34
|
-
return path.join(cwd, '.cursor', 'mcp.json')
|
|
35
|
-
case 'claude':
|
|
36
|
-
return path.join(cwd, '.claude', 'mcp.json')
|
|
37
|
-
case 'windsurf':
|
|
38
|
-
return path.join(cwd, 'windsurf.config.json')
|
|
39
|
-
default:
|
|
40
|
-
return ''
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function getConfigPatch(agent: string): any {
|
|
45
|
-
switch (agent) {
|
|
46
|
-
case 'cursor':
|
|
47
|
-
return {
|
|
48
|
-
mcpServers: {
|
|
49
|
-
'integration-app-agent': {
|
|
50
|
-
url: MCP_URL,
|
|
51
|
-
},
|
|
52
|
-
},
|
|
53
|
-
}
|
|
54
|
-
case 'claude':
|
|
55
|
-
return {
|
|
56
|
-
mcpServers: {
|
|
57
|
-
'integration-app-agent': {
|
|
58
|
-
url: MCP_URL,
|
|
59
|
-
},
|
|
60
|
-
},
|
|
61
|
-
}
|
|
62
|
-
case 'windsurf':
|
|
63
|
-
return {
|
|
64
|
-
mcpServers: [
|
|
65
|
-
{
|
|
66
|
-
name: 'integration-app-agent',
|
|
67
|
-
url: MCP_URL,
|
|
68
|
-
},
|
|
69
|
-
],
|
|
70
|
-
}
|
|
71
|
-
default:
|
|
72
|
-
return {}
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
function mergeConfig(agent: string, existing: any, patch: any): any {
|
|
77
|
-
if (agent === 'windsurf') {
|
|
78
|
-
// Overwrite or append to mcpServers array
|
|
79
|
-
const servers = Array.isArray(existing?.mcpServers) ? existing.mcpServers : []
|
|
80
|
-
const filtered = servers.filter((s: any) => s.name !== 'integration-app-agent')
|
|
81
|
-
return { ...existing, mcpServers: [...filtered, ...patch.mcpServers] }
|
|
82
|
-
}
|
|
83
|
-
// For Cursor/Claude, deep merge mcpServers
|
|
84
|
-
return {
|
|
85
|
-
...existing,
|
|
86
|
-
mcpServers: {
|
|
87
|
-
...existing?.mcpServers,
|
|
88
|
-
...patch.mcpServers,
|
|
89
|
-
},
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
interface AddMcpServerScreenProps {
|
|
94
|
-
onExit?: () => void
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
export const AddMcpServerScreen = ({ onExit }: AddMcpServerScreenProps) => {
|
|
98
|
-
const { exit } = useApp()
|
|
99
|
-
const cwd = useCwd()
|
|
100
|
-
const [step, setStep] = useState<'choose' | 'summary' | 'confirm' | 'done'>('choose')
|
|
101
|
-
const [agent, setAgent] = useState<string>('')
|
|
102
|
-
const [result, setResult] = useState<string>('')
|
|
103
|
-
const [error, setError] = useState<string>('')
|
|
104
|
-
|
|
105
|
-
useInput((input, key) => {
|
|
106
|
-
if (step === 'summary') {
|
|
107
|
-
if (input === 'y') setStep('confirm')
|
|
108
|
-
if (input === 'n' || key.escape) {
|
|
109
|
-
onExit?.()
|
|
110
|
-
exit()
|
|
111
|
-
}
|
|
112
|
-
return
|
|
113
|
-
}
|
|
114
|
-
if (key.escape) {
|
|
115
|
-
onExit?.()
|
|
116
|
-
exit()
|
|
117
|
-
}
|
|
118
|
-
if (step === 'done' && (input === 'q' || key.return)) {
|
|
119
|
-
onExit?.()
|
|
120
|
-
exit()
|
|
121
|
-
}
|
|
122
|
-
})
|
|
123
|
-
|
|
124
|
-
useEffect(() => {
|
|
125
|
-
if (step === 'confirm') {
|
|
126
|
-
try {
|
|
127
|
-
const configPath = getConfigPath(agent, cwd)
|
|
128
|
-
const patch = getConfigPatch(agent)
|
|
129
|
-
let existing = {}
|
|
130
|
-
if (fs.existsSync(configPath)) {
|
|
131
|
-
try {
|
|
132
|
-
existing = JSON.parse(fs.readFileSync(configPath, 'utf8'))
|
|
133
|
-
} catch {
|
|
134
|
-
existing = {}
|
|
135
|
-
}
|
|
136
|
-
} else {
|
|
137
|
-
// Ensure directory exists for .cursor/.claude
|
|
138
|
-
const dir = path.dirname(configPath)
|
|
139
|
-
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true })
|
|
140
|
-
}
|
|
141
|
-
const merged = mergeConfig(agent, existing, patch)
|
|
142
|
-
fs.writeFileSync(configPath, JSON.stringify(merged, null, 2))
|
|
143
|
-
setResult(`MCP server added to ${configPath}`)
|
|
144
|
-
setStep('done')
|
|
145
|
-
} catch (e: any) {
|
|
146
|
-
setError(e?.message || 'Unknown error')
|
|
147
|
-
setStep('done')
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
}, [step, agent, cwd])
|
|
151
|
-
|
|
152
|
-
const handleSelect = (item: { value: string }) => {
|
|
153
|
-
setAgent(item.value)
|
|
154
|
-
setStep('summary')
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
if (step === 'choose') {
|
|
158
|
-
return (
|
|
159
|
-
<Box flexDirection='column'>
|
|
160
|
-
<Text>Select your coding agent to add the MCP server:</Text>
|
|
161
|
-
<Box marginTop={1}>
|
|
162
|
-
<SelectInput items={AGENTS} onSelect={handleSelect} />
|
|
163
|
-
</Box>
|
|
164
|
-
<Box marginTop={1}>
|
|
165
|
-
<Text color='grey'>Press ESC to cancel</Text>
|
|
166
|
-
</Box>
|
|
167
|
-
</Box>
|
|
168
|
-
)
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
if (step === 'summary') {
|
|
172
|
-
return (
|
|
173
|
-
<Box flexDirection='column'>
|
|
174
|
-
<Text>{getSummary(agent)}</Text>
|
|
175
|
-
<Box marginTop={1}>
|
|
176
|
-
<Text>Proceed? (y/n)</Text>
|
|
177
|
-
</Box>
|
|
178
|
-
<Box marginTop={1}>
|
|
179
|
-
<Text color='grey'>Press ESC to cancel</Text>
|
|
180
|
-
</Box>
|
|
181
|
-
<Box>
|
|
182
|
-
<Text>
|
|
183
|
-
<Text color='green'>[y]</Text> Yes{' '}
|
|
184
|
-
<Text color='red'>[n]</Text> No
|
|
185
|
-
</Text>
|
|
186
|
-
</Box>
|
|
187
|
-
</Box>
|
|
188
|
-
)
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
if (step === 'confirm') {
|
|
192
|
-
return (
|
|
193
|
-
<Box>
|
|
194
|
-
<Text>Adding MCP server...</Text>
|
|
195
|
-
</Box>
|
|
196
|
-
)
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
if (step === 'done') {
|
|
200
|
-
return (
|
|
201
|
-
<Box flexDirection='column'>
|
|
202
|
-
{result && <Text color='green'>{result}</Text>}
|
|
203
|
-
{error && <Text color='red'>Error: {error}</Text>}
|
|
204
|
-
<Box marginTop={1}>
|
|
205
|
-
<Text color='grey'>Press Q or Enter to exit</Text>
|
|
206
|
-
</Box>
|
|
207
|
-
</Box>
|
|
208
|
-
)
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
// fallback
|
|
212
|
-
return null
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
// To use this screen, render <AddMcpServerScreen /> from your main app or a menu.
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import { Box, Text } from 'ink'
|
|
2
|
-
|
|
3
|
-
export const AgentStatus = () => {
|
|
4
|
-
const width = Math.min(100, process.stdout.columns || 100)
|
|
5
|
-
return (
|
|
6
|
-
<Box flexDirection='column' paddingX={1} borderStyle='round' borderTop width={width}>
|
|
7
|
-
{/* Header row with robot emoji and status */}
|
|
8
|
-
<Box marginTop={-1} marginBottom={1}>
|
|
9
|
-
<Text bold>
|
|
10
|
-
🤖 Membrane Code Agent — <Text color='green'>active</Text>
|
|
11
|
-
</Text>
|
|
12
|
-
</Box>
|
|
13
|
-
</Box>
|
|
14
|
-
)
|
|
15
|
-
}
|
package/src/components/Main.tsx
DELETED
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
import { exec } from 'node:child_process'
|
|
2
|
-
|
|
3
|
-
import React, { useState } from 'react'
|
|
4
|
-
import { Box, Text, useInput } from 'ink'
|
|
5
|
-
|
|
6
|
-
import { AgentStatus } from './AgentStatus'
|
|
7
|
-
import { SelectWorkspace } from './SelectWorkspace'
|
|
8
|
-
import { Setup } from './Setup'
|
|
9
|
-
import { WorkspaceSync } from './WorkspaceStatus'
|
|
10
|
-
import { getWorkspaceId } from '../modules/api/workspace-api-client'
|
|
11
|
-
import { useCwd } from '../modules/config/cwd-context'
|
|
12
|
-
import { useStatus } from '../modules/status/useStatus'
|
|
13
|
-
|
|
14
|
-
export const Main = () => {
|
|
15
|
-
const { isSetupComplete, workspaceKey, config } = useStatus()
|
|
16
|
-
const [showWorkspaceSelection, setShowWorkspaceSelection] = useState(false)
|
|
17
|
-
const [showSetup, setShowSetup] = useState(false)
|
|
18
|
-
const cwd = useCwd()
|
|
19
|
-
|
|
20
|
-
useInput((input) => {
|
|
21
|
-
// Only process hotkeys if main screen is visible
|
|
22
|
-
if (!showWorkspaceSelection && !showSetup) {
|
|
23
|
-
if (input === 'w') setShowWorkspaceSelection(true)
|
|
24
|
-
if (input === 'o' && workspaceKey && config?.workspaceSecret) {
|
|
25
|
-
// Fetch workspace ID and open console asynchronously
|
|
26
|
-
void (async () => {
|
|
27
|
-
try {
|
|
28
|
-
const workspaceId = await getWorkspaceId(cwd)
|
|
29
|
-
const url = `https://console.integration.app/w/${workspaceId}`
|
|
30
|
-
// Open URL in default browser
|
|
31
|
-
const command = process.platform === 'darwin' ? 'open' : process.platform === 'win32' ? 'start' : 'xdg-open'
|
|
32
|
-
exec(`${command} "${url}"`)
|
|
33
|
-
} catch (error) {
|
|
34
|
-
console.error('Failed to open workspace:', error)
|
|
35
|
-
}
|
|
36
|
-
})()
|
|
37
|
-
}
|
|
38
|
-
if (input === 's') {
|
|
39
|
-
setShowSetup(true)
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
})
|
|
43
|
-
|
|
44
|
-
if (showWorkspaceSelection) {
|
|
45
|
-
return <SelectWorkspace onExit={() => setShowWorkspaceSelection(false)} />
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
if (showSetup || !isSetupComplete) {
|
|
49
|
-
// Show only Setup during initial setup or re-setup
|
|
50
|
-
return <Setup key={Date.now()} onComplete={() => setShowSetup(false)} />
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
return (
|
|
54
|
-
<Box flexDirection='column'>
|
|
55
|
-
<Box flexGrow={1}>
|
|
56
|
-
<AgentStatus />
|
|
57
|
-
</Box>
|
|
58
|
-
<WorkspaceSync />
|
|
59
|
-
<Box paddingLeft={2}>
|
|
60
|
-
<Text color='grey'>[s: (re-)setup]</Text>
|
|
61
|
-
</Box>
|
|
62
|
-
</Box>
|
|
63
|
-
)
|
|
64
|
-
}
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import React from 'react'
|
|
2
|
-
import { Box } from 'ink'
|
|
3
|
-
|
|
4
|
-
import type { BoxProps } from 'ink'
|
|
5
|
-
|
|
6
|
-
type OverviewSectionProps = {
|
|
7
|
-
children: React.ReactNode
|
|
8
|
-
} & BoxProps
|
|
9
|
-
|
|
10
|
-
export const OverviewSection = ({ children, ...props }: OverviewSectionProps) => {
|
|
11
|
-
return (
|
|
12
|
-
<Box
|
|
13
|
-
borderStyle='round'
|
|
14
|
-
borderColor='lime'
|
|
15
|
-
paddingX={2}
|
|
16
|
-
paddingY={1}
|
|
17
|
-
alignSelf='flex-start'
|
|
18
|
-
flexDirection='column'
|
|
19
|
-
{...props}
|
|
20
|
-
>
|
|
21
|
-
{children}
|
|
22
|
-
</Box>
|
|
23
|
-
)
|
|
24
|
-
}
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
import React, { useState } from 'react'
|
|
2
|
-
import { Box, Text } from 'ink'
|
|
3
|
-
import Spinner from 'ink-spinner'
|
|
4
|
-
import InkTextInput from 'ink-text-input'
|
|
5
|
-
|
|
6
|
-
interface PersonalAccessTokenInputProps {
|
|
7
|
-
currentPat?: string
|
|
8
|
-
onSubmit: (pat: string) => Promise<void>
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export const PersonalAccessTokenInput = ({ currentPat, onSubmit }: PersonalAccessTokenInputProps) => {
|
|
12
|
-
const [patInput, setPatInput] = useState('')
|
|
13
|
-
const [patLoading, setPatLoading] = useState(false)
|
|
14
|
-
const [patError, setPatError] = useState<string | null>(null)
|
|
15
|
-
|
|
16
|
-
const handleSubmit = async (input: string) => {
|
|
17
|
-
setPatError(null)
|
|
18
|
-
setPatLoading(true)
|
|
19
|
-
try {
|
|
20
|
-
await onSubmit(input)
|
|
21
|
-
setPatInput('')
|
|
22
|
-
} catch (err: any) {
|
|
23
|
-
setPatError('Invalid token. Please try again.')
|
|
24
|
-
} finally {
|
|
25
|
-
setPatLoading(false)
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
return (
|
|
30
|
-
<Box flexDirection='column' borderStyle='round' borderTop width={70} paddingX={1}>
|
|
31
|
-
<Box marginTop={-1} marginBottom={1}>
|
|
32
|
-
<Text bold>🔑 Enter your Personal Access Token</Text>
|
|
33
|
-
</Box>
|
|
34
|
-
<Text>Please provide your Personal Access Token. You can find it here:</Text>
|
|
35
|
-
<Box marginTop={1} marginBottom={1}>
|
|
36
|
-
<Text color='yellow'>https://console.integration.app/w/0/manage-account/api-token</Text>
|
|
37
|
-
</Box>
|
|
38
|
-
{currentPat && <Text dimColor>Press Enter to keep your current token or type a new one.</Text>}
|
|
39
|
-
<InkTextInput
|
|
40
|
-
mask='*'
|
|
41
|
-
placeholder={`${currentPat ? '******' : 'Enter your token here...'}`}
|
|
42
|
-
value={patInput}
|
|
43
|
-
onChange={setPatInput}
|
|
44
|
-
onSubmit={handleSubmit}
|
|
45
|
-
/>
|
|
46
|
-
{patLoading && (
|
|
47
|
-
<Box marginTop={1}>
|
|
48
|
-
<Text>
|
|
49
|
-
<Spinner type='dots' /> Validating token...
|
|
50
|
-
</Text>
|
|
51
|
-
</Box>
|
|
52
|
-
)}
|
|
53
|
-
{patError && <Text color='red'>{patError}</Text>}
|
|
54
|
-
</Box>
|
|
55
|
-
)
|
|
56
|
-
}
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
import { Box, Text } from 'ink'
|
|
2
|
-
|
|
3
|
-
import { useFileWatcher } from '../contexts/FileWatcherContext'
|
|
4
|
-
import { FileChangeEventType } from '../modules/file-watcher/types'
|
|
5
|
-
|
|
6
|
-
const getSymbolAndColor = (type: FileChangeEventType) => {
|
|
7
|
-
switch (type) {
|
|
8
|
-
case 'added':
|
|
9
|
-
return { symbol: '+', color: 'green' as const }
|
|
10
|
-
case 'changed':
|
|
11
|
-
return { symbol: '*', color: 'yellow' as const }
|
|
12
|
-
case 'deleted':
|
|
13
|
-
return { symbol: '-', color: 'red' as const }
|
|
14
|
-
default:
|
|
15
|
-
return { symbol: '?', color: 'gray' as const }
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
const getActionText = (type: FileChangeEventType) => {
|
|
20
|
-
switch (type) {
|
|
21
|
-
case 'added':
|
|
22
|
-
return 'ADD'
|
|
23
|
-
case 'changed':
|
|
24
|
-
return 'UPD'
|
|
25
|
-
case 'deleted':
|
|
26
|
-
return 'DEL'
|
|
27
|
-
default:
|
|
28
|
-
return 'UNKNOWN'
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export function RecentChanges() {
|
|
33
|
-
const { recentChanges } = useFileWatcher()
|
|
34
|
-
|
|
35
|
-
const sortedRecentChanges = [...recentChanges].sort((a, b) => a.timestamp - b.timestamp)
|
|
36
|
-
|
|
37
|
-
if (sortedRecentChanges.length === 0) {
|
|
38
|
-
return (
|
|
39
|
-
<Box paddingTop={1}>
|
|
40
|
-
<Text color='gray'>No recent changes</Text>
|
|
41
|
-
</Box>
|
|
42
|
-
)
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
return (
|
|
46
|
-
<Box flexDirection='column' paddingTop={1}>
|
|
47
|
-
<Text color='gray'>Recent Changes:</Text>
|
|
48
|
-
{sortedRecentChanges.map((change, _) => {
|
|
49
|
-
const { symbol, color } = getSymbolAndColor(change.type)
|
|
50
|
-
const actionText = getActionText(change.type)
|
|
51
|
-
|
|
52
|
-
return (
|
|
53
|
-
<Box key={`${change.filePath}-${change.timestamp}`} marginLeft={1}>
|
|
54
|
-
<Text color={color}>
|
|
55
|
-
{symbol} {actionText}:{' '}
|
|
56
|
-
</Text>
|
|
57
|
-
<Text>
|
|
58
|
-
[{change.elementType}] <Text bold>{change.elementKey}</Text> ({change.relativePath})
|
|
59
|
-
</Text>
|
|
60
|
-
</Box>
|
|
61
|
-
)
|
|
62
|
-
})}
|
|
63
|
-
</Box>
|
|
64
|
-
)
|
|
65
|
-
}
|
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
import React, { useState } from 'react'
|
|
2
|
-
import { Select, TextInput } from '@inkjs/ui'
|
|
3
|
-
import { Box, Text, useInput } from 'ink'
|
|
4
|
-
import Spinner from 'ink-spinner'
|
|
5
|
-
import useSWRImmutable from 'swr/immutable'
|
|
6
|
-
|
|
7
|
-
import { useProjectConfig } from '../modules/config/project'
|
|
8
|
-
|
|
9
|
-
import type { OrgWorkspace } from '@integration-app/sdk'
|
|
10
|
-
|
|
11
|
-
interface SelectWorkspaceProps {
|
|
12
|
-
onExit?: () => void
|
|
13
|
-
showEscOption?: boolean
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export const SelectWorkspace = ({ onExit, showEscOption = true }: SelectWorkspaceProps) => {
|
|
17
|
-
const [searchQuery, setSearchQuery] = useState('')
|
|
18
|
-
const { data, error, isLoading: isLoadingWorkspaces } = useSWRImmutable('/account')
|
|
19
|
-
const { updateConfig, isLoading: isLoadingConfig } = useProjectConfig()
|
|
20
|
-
|
|
21
|
-
const workspaces = (data as any)?.workspaces as OrgWorkspace[]
|
|
22
|
-
const isLoading = isLoadingWorkspaces || isLoadingConfig
|
|
23
|
-
|
|
24
|
-
useInput((input, key) => {
|
|
25
|
-
if (key.escape) {
|
|
26
|
-
onExit?.()
|
|
27
|
-
}
|
|
28
|
-
})
|
|
29
|
-
|
|
30
|
-
if (isLoading) {
|
|
31
|
-
return (
|
|
32
|
-
<Box>
|
|
33
|
-
<Spinner />
|
|
34
|
-
<Text> Fetching workspaces...</Text>
|
|
35
|
-
</Box>
|
|
36
|
-
)
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
if (error) {
|
|
40
|
-
return (
|
|
41
|
-
<Box flexDirection='column'>
|
|
42
|
-
<Text color='red'>Error: {error.message}</Text>
|
|
43
|
-
<Box marginTop={1}>
|
|
44
|
-
<Text color='grey'>Press ESC to go back</Text>
|
|
45
|
-
</Box>
|
|
46
|
-
</Box>
|
|
47
|
-
)
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
const filteredWorkspaces = workspaces?.filter((ws) => ws.name.toLowerCase().includes(searchQuery.toLowerCase())) ?? []
|
|
51
|
-
|
|
52
|
-
const items = filteredWorkspaces.map((ws) => ({
|
|
53
|
-
label: ws.name,
|
|
54
|
-
value: ws.id,
|
|
55
|
-
}))
|
|
56
|
-
|
|
57
|
-
const showingWorkspaces = items.length
|
|
58
|
-
const totalWorkspaces = workspaces?.length ?? 0
|
|
59
|
-
|
|
60
|
-
const handleSelect = async (value: string) => {
|
|
61
|
-
const selectedWorkspace = filteredWorkspaces.find((ws) => ws.id === value)
|
|
62
|
-
if (!selectedWorkspace) {
|
|
63
|
-
return
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
const { key, secret } = selectedWorkspace as any
|
|
67
|
-
if (!key || !secret) {
|
|
68
|
-
// TODO: handle error
|
|
69
|
-
return
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
await updateConfig({
|
|
73
|
-
workspaceKey: key,
|
|
74
|
-
workspaceSecret: secret,
|
|
75
|
-
})
|
|
76
|
-
|
|
77
|
-
// Close the selection screen after successful workspace change
|
|
78
|
-
onExit?.()
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
return (
|
|
82
|
-
<Box flexDirection='column' borderStyle='round' borderTop width={70} paddingX={1}>
|
|
83
|
-
<Box marginTop={-1}>
|
|
84
|
-
<Text bold>📁 Select your workspace</Text>
|
|
85
|
-
</Box>
|
|
86
|
-
<Box marginTop={1}>
|
|
87
|
-
<Text>Search: </Text>
|
|
88
|
-
<TextInput placeholder='Enter a search query...' onChange={setSearchQuery} />
|
|
89
|
-
</Box>
|
|
90
|
-
{totalWorkspaces > 5 && (
|
|
91
|
-
<Text>
|
|
92
|
-
Showing {showingWorkspaces} of {totalWorkspaces} workspaces.
|
|
93
|
-
</Text>
|
|
94
|
-
)}
|
|
95
|
-
<Box marginTop={1}>
|
|
96
|
-
<Select
|
|
97
|
-
options={items}
|
|
98
|
-
onChange={(value) => {
|
|
99
|
-
if (value) {
|
|
100
|
-
void handleSelect(value)
|
|
101
|
-
}
|
|
102
|
-
}}
|
|
103
|
-
/>
|
|
104
|
-
</Box>
|
|
105
|
-
{showEscOption && (
|
|
106
|
-
<Box marginTop={1}>
|
|
107
|
-
<Text color='grey'>Press ESC to go back</Text>
|
|
108
|
-
</Box>
|
|
109
|
-
)}
|
|
110
|
-
</Box>
|
|
111
|
-
)
|
|
112
|
-
}
|
package/src/components/Setup.tsx
DELETED
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
import React, { useState } from 'react'
|
|
2
|
-
import { Box, Text, useInput } from 'ink'
|
|
3
|
-
import Spinner from 'ink-spinner'
|
|
4
|
-
|
|
5
|
-
import { PersonalAccessTokenInput } from './PersonalAccessTokenInput'
|
|
6
|
-
import { SelectWorkspace } from './SelectWorkspace'
|
|
7
|
-
import { AccountApiClient } from '../modules/api/account-api-client'
|
|
8
|
-
import { setPat, getPat } from '../modules/config/system'
|
|
9
|
-
import { Step, STEP_LABELS } from '../modules/setup/steps'
|
|
10
|
-
import { useSetup } from '../modules/setup/useSetup'
|
|
11
|
-
|
|
12
|
-
type StepStatus = 'pending' | 'current' | 'done'
|
|
13
|
-
|
|
14
|
-
const StepDisplay = ({ status, label }: { status: StepStatus; label: string }) => {
|
|
15
|
-
return (
|
|
16
|
-
<Box>
|
|
17
|
-
<Box width={2}>
|
|
18
|
-
{status === 'current' && <Spinner type='dots' />}
|
|
19
|
-
{status === 'done' && <Text>✅</Text>}
|
|
20
|
-
</Box>
|
|
21
|
-
<Text dimColor={status !== 'current'}>{label}</Text>
|
|
22
|
-
</Box>
|
|
23
|
-
)
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
const ALL_STEPS = [Step.Authenticate, Step.ConnectWorkspace]
|
|
27
|
-
|
|
28
|
-
interface SetupProps {
|
|
29
|
-
onComplete?: () => void
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export const Setup = ({ onComplete }: SetupProps) => {
|
|
33
|
-
const [completed, setCompleted] = useState(false)
|
|
34
|
-
const [currentStepIndex, setCurrentStepIndex] = useState(0)
|
|
35
|
-
const [patLoading, setPatLoading] = useState(false)
|
|
36
|
-
const [patError, setPatError] = useState<string | null>(null)
|
|
37
|
-
const [patInput, setPatInput] = useState('')
|
|
38
|
-
|
|
39
|
-
const { isSetupComplete } = useSetup()
|
|
40
|
-
|
|
41
|
-
const currentStep = ALL_STEPS[currentStepIndex]
|
|
42
|
-
const stepIndex = currentStepIndex + 1
|
|
43
|
-
const totalSteps = ALL_STEPS.length
|
|
44
|
-
|
|
45
|
-
const steps = ALL_STEPS.map((step, idx) => {
|
|
46
|
-
let status: StepStatus = 'pending'
|
|
47
|
-
if (idx < currentStepIndex) {
|
|
48
|
-
status = 'done'
|
|
49
|
-
} else if (idx === currentStepIndex) {
|
|
50
|
-
status = 'current'
|
|
51
|
-
}
|
|
52
|
-
return { id: step, label: STEP_LABELS[step], status }
|
|
53
|
-
})
|
|
54
|
-
|
|
55
|
-
const currentPat = getPat()
|
|
56
|
-
const patMask = currentPat ? '*'.repeat(currentPat.length) : ''
|
|
57
|
-
|
|
58
|
-
const handlePatSubmit = async (input: string) => {
|
|
59
|
-
setPatError(null)
|
|
60
|
-
setPatLoading(true)
|
|
61
|
-
const patToTest = currentPat && input === '' ? currentPat : input
|
|
62
|
-
const testClient = new AccountApiClient()
|
|
63
|
-
try {
|
|
64
|
-
await testClient.request('/account', { headers: { Authorization: `Bearer ${patToTest}` } })
|
|
65
|
-
setPat(patToTest)
|
|
66
|
-
setCurrentStepIndex((idx) => idx + 1)
|
|
67
|
-
setPatInput('')
|
|
68
|
-
} catch (err: any) {
|
|
69
|
-
console.log(err)
|
|
70
|
-
setPatError('Invalid token. Please try again.')
|
|
71
|
-
} finally {
|
|
72
|
-
setPatLoading(false)
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
const handleWorkspaceSelected = () => {
|
|
77
|
-
setCompleted(true)
|
|
78
|
-
if (onComplete) onComplete()
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
useInput((input, key) => {
|
|
82
|
-
if (isSetupComplete && key.escape && onComplete) {
|
|
83
|
-
onComplete()
|
|
84
|
-
}
|
|
85
|
-
})
|
|
86
|
-
|
|
87
|
-
return completed ? (
|
|
88
|
-
<Box>
|
|
89
|
-
<Text>✅ Setup complete. You are ready to go!</Text>
|
|
90
|
-
</Box>
|
|
91
|
-
) : (
|
|
92
|
-
<Box flexDirection='column' alignSelf='flex-start' gap={1}>
|
|
93
|
-
{/* Setup block header */}
|
|
94
|
-
<Box flexDirection='column' paddingX={1} borderStyle='round' borderTop width={70}>
|
|
95
|
-
<Box marginTop={-1} marginBottom={1}>
|
|
96
|
-
<Text bold>
|
|
97
|
-
🛠️ Setup —{' '}
|
|
98
|
-
<Text color='cyan'>
|
|
99
|
-
Step {stepIndex} of {totalSteps}
|
|
100
|
-
</Text>
|
|
101
|
-
{isSetupComplete && <Text color='grey'> [esc: go back]</Text>}
|
|
102
|
-
</Text>
|
|
103
|
-
</Box>
|
|
104
|
-
<Box flexDirection='column' paddingLeft={2}>
|
|
105
|
-
{steps.map((step) => (
|
|
106
|
-
<StepDisplay key={step.id} status={step.status} label={step.label} />
|
|
107
|
-
))}
|
|
108
|
-
</Box>
|
|
109
|
-
</Box>
|
|
110
|
-
|
|
111
|
-
{/* Step blocks with header */}
|
|
112
|
-
{currentStep === Step.Authenticate && (
|
|
113
|
-
<PersonalAccessTokenInput currentPat={currentPat} onSubmit={handlePatSubmit} />
|
|
114
|
-
)}
|
|
115
|
-
|
|
116
|
-
{currentStep === Step.ConnectWorkspace && (
|
|
117
|
-
<SelectWorkspace onExit={handleWorkspaceSelected} showEscOption={false} />
|
|
118
|
-
)}
|
|
119
|
-
</Box>
|
|
120
|
-
)
|
|
121
|
-
}
|