prjct-cli 0.17.0 → 0.18.1
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/core/__tests__/agentic/memory-system.test.ts +2 -1
- package/core/__tests__/agentic/plan-mode.test.ts +2 -1
- package/core/agentic/agent-router.ts +21 -75
- package/core/command-registry/setup-commands.ts +15 -0
- package/core/domain/agent-generator.ts +9 -17
- package/core/infrastructure/path-manager.ts +23 -1
- package/core/storage/ideas-storage.ts +4 -0
- package/core/storage/queue-storage.ts +4 -0
- package/core/storage/shipped-storage.ts +4 -0
- package/core/storage/state-storage.ts +4 -0
- package/core/storage/storage-manager.ts +10 -7
- package/core/sync/auth-config.ts +145 -0
- package/core/sync/index.ts +30 -0
- package/core/sync/oauth-handler.ts +148 -0
- package/core/sync/sync-client.ts +252 -0
- package/core/sync/sync-manager.ts +358 -0
- package/package.json +1 -1
- package/templates/commands/auth.md +234 -0
- package/templates/commands/sync.md +91 -0
|
@@ -29,7 +29,8 @@ describe('MemorySystem P3.3', () => {
|
|
|
29
29
|
userTriggered: true
|
|
30
30
|
})
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
// UUID format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
|
33
|
+
expect(memoryId).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i)
|
|
33
34
|
|
|
34
35
|
const memories = await memorySystem.getAllMemories(TEST_PROJECT_ID)
|
|
35
36
|
expect(memories.length).toBe(1)
|
|
@@ -92,7 +92,8 @@ describe('PlanMode P3.4', () => {
|
|
|
92
92
|
it('should create a new plan with correct initial state', () => {
|
|
93
93
|
const plan = planMode.startPlanning(TEST_PROJECT_ID, 'feature', { description: 'Add dark mode' })
|
|
94
94
|
|
|
95
|
-
|
|
95
|
+
// UUID format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
|
96
|
+
expect(plan.id).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i)
|
|
96
97
|
expect(plan.projectId).toBe(TEST_PROJECT_ID)
|
|
97
98
|
expect(plan.command).toBe('feature')
|
|
98
99
|
expect(plan.status).toBe(PLAN_STATUS.GATHERING)
|
|
@@ -2,14 +2,13 @@
|
|
|
2
2
|
* Agent Router
|
|
3
3
|
* Orchestrates agent loading and context building for Claude delegation.
|
|
4
4
|
*
|
|
5
|
-
* Loads agents from
|
|
6
|
-
*
|
|
7
|
-
* 2. ~/.prjct-cli/projects/{id}/agents/ - Legacy agents (fallback)
|
|
5
|
+
* Loads agents from global storage:
|
|
6
|
+
* ~/.prjct-cli/projects/{id}/agents/
|
|
8
7
|
*
|
|
9
|
-
*
|
|
8
|
+
* Agents are dynamically generated by /p:sync based on project tech stack.
|
|
10
9
|
*
|
|
11
10
|
* @module agentic/agent-router
|
|
12
|
-
* @version
|
|
11
|
+
* @version 4.0.0
|
|
13
12
|
*/
|
|
14
13
|
|
|
15
14
|
import fs from 'fs/promises'
|
|
@@ -20,7 +19,6 @@ import pathManager from '../infrastructure/path-manager'
|
|
|
20
19
|
interface Agent {
|
|
21
20
|
name: string
|
|
22
21
|
content: string
|
|
23
|
-
source: 'claude-code' | 'legacy'
|
|
24
22
|
}
|
|
25
23
|
|
|
26
24
|
interface AssignmentContext {
|
|
@@ -38,15 +36,7 @@ interface AssignmentContext {
|
|
|
38
36
|
class AgentRouter {
|
|
39
37
|
projectId: string | null = null
|
|
40
38
|
projectPath: string | null = null
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Get path to Claude Code sub-agents directory
|
|
45
|
-
*/
|
|
46
|
-
getClaudeCodeAgentsPath(): string | null {
|
|
47
|
-
if (!this.projectPath) return null
|
|
48
|
-
return path.join(this.projectPath, '.claude', 'agents')
|
|
49
|
-
}
|
|
39
|
+
agentsPath: string | null = null
|
|
50
40
|
|
|
51
41
|
/**
|
|
52
42
|
* Initialize router with project context
|
|
@@ -54,25 +44,24 @@ class AgentRouter {
|
|
|
54
44
|
async initialize(projectPath: string): Promise<void> {
|
|
55
45
|
this.projectId = await configManager.getProjectId(projectPath)
|
|
56
46
|
this.projectPath = projectPath
|
|
57
|
-
this.
|
|
47
|
+
this.agentsPath = pathManager.getFilePath(this.projectId!, 'agents', '')
|
|
58
48
|
}
|
|
59
49
|
|
|
60
50
|
/**
|
|
61
|
-
* Load agents from
|
|
51
|
+
* Load all available agents from global storage
|
|
62
52
|
*/
|
|
63
|
-
|
|
64
|
-
agentsPath
|
|
65
|
-
|
|
66
|
-
): Promise<Agent[]> {
|
|
53
|
+
async loadAvailableAgents(): Promise<Agent[]> {
|
|
54
|
+
if (!this.agentsPath) return []
|
|
55
|
+
|
|
67
56
|
try {
|
|
68
|
-
const files = await fs.readdir(agentsPath)
|
|
57
|
+
const files = await fs.readdir(this.agentsPath)
|
|
69
58
|
const agents: Agent[] = []
|
|
70
59
|
|
|
71
60
|
for (const file of files) {
|
|
72
61
|
if (file.endsWith('.md')) {
|
|
73
62
|
const name = file.replace('.md', '')
|
|
74
|
-
const content = await fs.readFile(path.join(agentsPath, file), 'utf-8')
|
|
75
|
-
agents.push({ name, content
|
|
63
|
+
const content = await fs.readFile(path.join(this.agentsPath, file), 'utf-8')
|
|
64
|
+
agents.push({ name, content })
|
|
76
65
|
}
|
|
77
66
|
}
|
|
78
67
|
|
|
@@ -82,33 +71,6 @@ class AgentRouter {
|
|
|
82
71
|
}
|
|
83
72
|
}
|
|
84
73
|
|
|
85
|
-
/**
|
|
86
|
-
* Load all available agents from both Claude Code and legacy locations
|
|
87
|
-
* Claude Code sub-agents take priority over legacy agents with same name
|
|
88
|
-
*/
|
|
89
|
-
async loadAvailableAgents(): Promise<Agent[]> {
|
|
90
|
-
const agentMap = new Map<string, Agent>()
|
|
91
|
-
|
|
92
|
-
// Load legacy agents first (lower priority)
|
|
93
|
-
if (this.legacyAgentsPath) {
|
|
94
|
-
const legacyAgents = await this.loadAgentsFromPath(this.legacyAgentsPath, 'legacy')
|
|
95
|
-
for (const agent of legacyAgents) {
|
|
96
|
-
agentMap.set(agent.name, agent)
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// Load Claude Code sub-agents (higher priority - overwrites legacy)
|
|
101
|
-
const claudeCodePath = this.getClaudeCodeAgentsPath()
|
|
102
|
-
if (claudeCodePath) {
|
|
103
|
-
const claudeCodeAgents = await this.loadAgentsFromPath(claudeCodePath, 'claude-code')
|
|
104
|
-
for (const agent of claudeCodeAgents) {
|
|
105
|
-
agentMap.set(agent.name, agent)
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
return Array.from(agentMap.values())
|
|
110
|
-
}
|
|
111
|
-
|
|
112
74
|
/**
|
|
113
75
|
* Get list of available agent names
|
|
114
76
|
*/
|
|
@@ -118,34 +80,18 @@ class AgentRouter {
|
|
|
118
80
|
}
|
|
119
81
|
|
|
120
82
|
/**
|
|
121
|
-
* Load a specific agent by name
|
|
122
|
-
* Checks Claude Code sub-agents first, then falls back to legacy
|
|
83
|
+
* Load a specific agent by name from global storage
|
|
123
84
|
*/
|
|
124
85
|
async loadAgent(name: string): Promise<Agent | null> {
|
|
125
|
-
|
|
126
|
-
const claudeCodePath = this.getClaudeCodeAgentsPath()
|
|
127
|
-
if (claudeCodePath) {
|
|
128
|
-
try {
|
|
129
|
-
const filePath = path.join(claudeCodePath, `${name}.md`)
|
|
130
|
-
const content = await fs.readFile(filePath, 'utf-8')
|
|
131
|
-
return { name, content, source: 'claude-code' }
|
|
132
|
-
} catch {
|
|
133
|
-
// Not found in Claude Code path, try legacy
|
|
134
|
-
}
|
|
135
|
-
}
|
|
86
|
+
if (!this.agentsPath) return null
|
|
136
87
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
} catch {
|
|
144
|
-
// Not found
|
|
145
|
-
}
|
|
88
|
+
try {
|
|
89
|
+
const filePath = path.join(this.agentsPath, `${name}.md`)
|
|
90
|
+
const content = await fs.readFile(filePath, 'utf-8')
|
|
91
|
+
return { name, content }
|
|
92
|
+
} catch {
|
|
93
|
+
return null
|
|
146
94
|
}
|
|
147
|
-
|
|
148
|
-
return null
|
|
149
95
|
}
|
|
150
96
|
|
|
151
97
|
/**
|
|
@@ -65,4 +65,19 @@ export const SETUP_COMMANDS: Command[] = [
|
|
|
65
65
|
requiresInit: false,
|
|
66
66
|
blockingRules: null,
|
|
67
67
|
},
|
|
68
|
+
|
|
69
|
+
{
|
|
70
|
+
name: 'auth',
|
|
71
|
+
category: 'setup',
|
|
72
|
+
description: 'Manage cloud authentication',
|
|
73
|
+
usage: {
|
|
74
|
+
claude: '/p:auth [login|logout|status]',
|
|
75
|
+
terminal: 'prjct auth [login|logout|status]',
|
|
76
|
+
},
|
|
77
|
+
params: '[login|logout|status]',
|
|
78
|
+
implemented: true,
|
|
79
|
+
hasTemplate: true,
|
|
80
|
+
requiresInit: false,
|
|
81
|
+
blockingRules: null,
|
|
82
|
+
},
|
|
68
83
|
]
|
|
@@ -38,9 +38,9 @@ class AgentGenerator {
|
|
|
38
38
|
|
|
39
39
|
constructor(projectId: string | null = null) {
|
|
40
40
|
this.projectId = projectId
|
|
41
|
-
//
|
|
41
|
+
// Write to agents/ for MD storage (matches AgentLoader)
|
|
42
42
|
this.outputDir = projectId
|
|
43
|
-
? path.join(os.homedir(), '.prjct-cli', 'projects', projectId, '
|
|
43
|
+
? path.join(os.homedir(), '.prjct-cli', 'projects', projectId, 'agents')
|
|
44
44
|
: path.join(os.homedir(), '.prjct-cli', 'agents')
|
|
45
45
|
this.loader = new AgentLoader(projectId)
|
|
46
46
|
}
|
|
@@ -54,18 +54,10 @@ class AgentGenerator {
|
|
|
54
54
|
log.debug(`Generating ${agentName} agent...`)
|
|
55
55
|
await fs.mkdir(this.outputDir, { recursive: true })
|
|
56
56
|
|
|
57
|
-
// Write as
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
domain: config.domain || 'general',
|
|
62
|
-
expertise: config.expertise || '',
|
|
63
|
-
contextFilter: config.contextFilter || 'Only relevant files',
|
|
64
|
-
createdAt: new Date().toISOString()
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
const outputPath = path.join(this.outputDir, `${agentName}.json`)
|
|
68
|
-
await fs.writeFile(outputPath, JSON.stringify(agent, null, 2), 'utf-8')
|
|
57
|
+
// Write as MD (matches AgentLoader which reads .md files)
|
|
58
|
+
const content = this.buildAgentPrompt(agentName, config)
|
|
59
|
+
const outputPath = path.join(this.outputDir, `${agentName}.md`)
|
|
60
|
+
await fs.writeFile(outputPath, content, 'utf-8')
|
|
69
61
|
log.debug(`${agentName} agent created`)
|
|
70
62
|
|
|
71
63
|
return { name: agentName }
|
|
@@ -135,10 +127,10 @@ ${config.contextFilter || 'Only relevant files'}
|
|
|
135
127
|
|
|
136
128
|
try {
|
|
137
129
|
const files = await fs.readdir(this.outputDir)
|
|
138
|
-
const agentFiles = files.filter((f) => f.endsWith('.
|
|
130
|
+
const agentFiles = files.filter((f) => f.endsWith('.md') && !f.startsWith('.'))
|
|
139
131
|
|
|
140
132
|
for (const file of agentFiles) {
|
|
141
|
-
const type = file.replace('.
|
|
133
|
+
const type = file.replace('.md', '')
|
|
142
134
|
|
|
143
135
|
if (!requiredAgents.includes(type)) {
|
|
144
136
|
const filePath = path.join(this.outputDir, file)
|
|
@@ -160,7 +152,7 @@ ${config.contextFilter || 'Only relevant files'}
|
|
|
160
152
|
async listAgents(): Promise<string[]> {
|
|
161
153
|
try {
|
|
162
154
|
const files = await fs.readdir(this.outputDir)
|
|
163
|
-
return files.filter((f) => f.endsWith('.
|
|
155
|
+
return files.filter((f) => f.endsWith('.md') && !f.startsWith('.')).map((f) => f.replace('.md', ''))
|
|
164
156
|
} catch {
|
|
165
157
|
return []
|
|
166
158
|
}
|
|
@@ -114,7 +114,7 @@ class PathManager {
|
|
|
114
114
|
|
|
115
115
|
const projectPath = this.getGlobalProjectPath(projectId)
|
|
116
116
|
|
|
117
|
-
const layers = ['core', 'progress', 'planning', 'analysis', 'memory']
|
|
117
|
+
const layers = ['core', 'progress', 'planning', 'analysis', 'memory', 'agents']
|
|
118
118
|
|
|
119
119
|
for (const layer of layers) {
|
|
120
120
|
await fileHelper.ensureDir(path.join(projectPath, layer))
|
|
@@ -252,6 +252,28 @@ class PathManager {
|
|
|
252
252
|
}
|
|
253
253
|
return absolutePath
|
|
254
254
|
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Get the auth config file path for cloud sync
|
|
258
|
+
* Stored in global config directory, not project-specific
|
|
259
|
+
*/
|
|
260
|
+
getAuthConfigPath(): string {
|
|
261
|
+
return path.join(this.globalConfigDir, 'auth.json')
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Get the sync pending events file path for a project
|
|
266
|
+
*/
|
|
267
|
+
getSyncPendingPath(projectId: string): string {
|
|
268
|
+
return path.join(this.getGlobalProjectPath(projectId), 'sync', 'pending.json')
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Get the last sync timestamp file path for a project
|
|
273
|
+
*/
|
|
274
|
+
getLastSyncPath(projectId: string): string {
|
|
275
|
+
return path.join(this.getGlobalProjectPath(projectId), 'sync', 'last-sync.json')
|
|
276
|
+
}
|
|
255
277
|
}
|
|
256
278
|
|
|
257
279
|
const pathManager = new PathManager()
|
|
@@ -42,6 +42,10 @@ class IdeasStorage extends StorageManager<IdeasJson> {
|
|
|
42
42
|
return 'ideas.md'
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
+
protected getLayer(): string {
|
|
46
|
+
return 'planning'
|
|
47
|
+
}
|
|
48
|
+
|
|
45
49
|
protected getEventType(action: 'update' | 'create' | 'delete'): string {
|
|
46
50
|
return `ideas.${action}d`
|
|
47
51
|
}
|
|
@@ -39,6 +39,10 @@ class ShippedStorage extends StorageManager<ShippedJson> {
|
|
|
39
39
|
return 'shipped.md'
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
+
protected getLayer(): string {
|
|
43
|
+
return 'progress'
|
|
44
|
+
}
|
|
45
|
+
|
|
42
46
|
protected getEventType(action: 'update' | 'create' | 'delete'): string {
|
|
43
47
|
return `shipped.${action}d`
|
|
44
48
|
}
|
|
@@ -13,6 +13,7 @@ import fs from 'fs/promises'
|
|
|
13
13
|
import path from 'path'
|
|
14
14
|
import os from 'os'
|
|
15
15
|
import { eventBus, type SyncEvent } from '../events'
|
|
16
|
+
import pathManager from '../infrastructure/path-manager'
|
|
16
17
|
|
|
17
18
|
interface CacheEntry<T> {
|
|
18
19
|
data: T
|
|
@@ -67,17 +68,19 @@ export abstract class StorageManager<T> {
|
|
|
67
68
|
|
|
68
69
|
/**
|
|
69
70
|
* Get file path for context MD
|
|
71
|
+
* Uses layer-based paths to match MdBaseManager structure
|
|
70
72
|
*/
|
|
71
73
|
protected getContextPath(projectId: string, mdFilename: string): string {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
'.prjct-cli/projects',
|
|
75
|
-
projectId,
|
|
76
|
-
'context',
|
|
77
|
-
mdFilename
|
|
78
|
-
)
|
|
74
|
+
const layer = this.getLayer()
|
|
75
|
+
return pathManager.getFilePath(projectId, layer, mdFilename)
|
|
79
76
|
}
|
|
80
77
|
|
|
78
|
+
/**
|
|
79
|
+
* Get the layer for context MD files
|
|
80
|
+
* Override in subclasses: 'core' | 'planning' | 'progress'
|
|
81
|
+
*/
|
|
82
|
+
protected abstract getLayer(): string
|
|
83
|
+
|
|
81
84
|
/**
|
|
82
85
|
* Get default data structure
|
|
83
86
|
*/
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auth Config - Manages API key storage for cloud sync
|
|
3
|
+
*
|
|
4
|
+
* Stores credentials in ~/.prjct-cli/config/auth.json
|
|
5
|
+
* Used by SyncClient to authenticate with prjct API
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import path from 'path'
|
|
9
|
+
import os from 'os'
|
|
10
|
+
import * as fileHelper from '../utils/file-helper'
|
|
11
|
+
|
|
12
|
+
export interface AuthConfig {
|
|
13
|
+
apiKey: string | null
|
|
14
|
+
apiUrl: string
|
|
15
|
+
userId: string | null
|
|
16
|
+
email: string | null
|
|
17
|
+
lastAuth: string | null
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const DEFAULT_API_URL = 'https://api.prjct.app'
|
|
21
|
+
|
|
22
|
+
const DEFAULT_CONFIG: AuthConfig = {
|
|
23
|
+
apiKey: null,
|
|
24
|
+
apiUrl: DEFAULT_API_URL,
|
|
25
|
+
userId: null,
|
|
26
|
+
email: null,
|
|
27
|
+
lastAuth: null,
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
class AuthConfigManager {
|
|
31
|
+
private configPath: string
|
|
32
|
+
private cachedConfig: AuthConfig | null = null
|
|
33
|
+
|
|
34
|
+
constructor() {
|
|
35
|
+
this.configPath = path.join(os.homedir(), '.prjct-cli', 'config', 'auth.json')
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Get the auth config file path
|
|
40
|
+
*/
|
|
41
|
+
getConfigPath(): string {
|
|
42
|
+
return this.configPath
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Read auth config from disk
|
|
47
|
+
*/
|
|
48
|
+
async read(): Promise<AuthConfig> {
|
|
49
|
+
if (this.cachedConfig) {
|
|
50
|
+
return this.cachedConfig
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const config = await fileHelper.readJson<AuthConfig>(this.configPath)
|
|
54
|
+
this.cachedConfig = config ?? { ...DEFAULT_CONFIG }
|
|
55
|
+
return this.cachedConfig
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Write auth config to disk
|
|
60
|
+
*/
|
|
61
|
+
async write(config: Partial<AuthConfig>): Promise<void> {
|
|
62
|
+
const current = await this.read()
|
|
63
|
+
const updated: AuthConfig = {
|
|
64
|
+
...current,
|
|
65
|
+
...config,
|
|
66
|
+
lastAuth: new Date().toISOString(),
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
await fileHelper.ensureDir(path.dirname(this.configPath))
|
|
70
|
+
await fileHelper.writeJson(this.configPath, updated)
|
|
71
|
+
this.cachedConfig = updated
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Check if user is authenticated (has valid API key)
|
|
76
|
+
*/
|
|
77
|
+
async hasAuth(): Promise<boolean> {
|
|
78
|
+
const config = await this.read()
|
|
79
|
+
return config.apiKey !== null && config.apiKey.length > 0
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Get the API key if available
|
|
84
|
+
*/
|
|
85
|
+
async getApiKey(): Promise<string | null> {
|
|
86
|
+
const config = await this.read()
|
|
87
|
+
return config.apiKey
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Get the API URL (allows override for dev/staging)
|
|
92
|
+
*/
|
|
93
|
+
async getApiUrl(): Promise<string> {
|
|
94
|
+
const config = await this.read()
|
|
95
|
+
return config.apiUrl || DEFAULT_API_URL
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Save API key and user info after successful auth
|
|
100
|
+
*/
|
|
101
|
+
async saveAuth(apiKey: string, userId: string, email: string): Promise<void> {
|
|
102
|
+
await this.write({
|
|
103
|
+
apiKey,
|
|
104
|
+
userId,
|
|
105
|
+
email,
|
|
106
|
+
})
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Clear all auth data (logout)
|
|
111
|
+
*/
|
|
112
|
+
async clearAuth(): Promise<void> {
|
|
113
|
+
this.cachedConfig = { ...DEFAULT_CONFIG }
|
|
114
|
+
await fileHelper.writeJson(this.configPath, this.cachedConfig)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Get auth status for display
|
|
119
|
+
*/
|
|
120
|
+
async getStatus(): Promise<{
|
|
121
|
+
authenticated: boolean
|
|
122
|
+
email: string | null
|
|
123
|
+
apiKeyPrefix: string | null
|
|
124
|
+
lastAuth: string | null
|
|
125
|
+
}> {
|
|
126
|
+
const config = await this.read()
|
|
127
|
+
|
|
128
|
+
return {
|
|
129
|
+
authenticated: config.apiKey !== null,
|
|
130
|
+
email: config.email,
|
|
131
|
+
apiKeyPrefix: config.apiKey ? config.apiKey.substring(0, 12) + '...' : null,
|
|
132
|
+
lastAuth: config.lastAuth,
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Clear cache (useful for testing)
|
|
138
|
+
*/
|
|
139
|
+
clearCache(): void {
|
|
140
|
+
this.cachedConfig = null
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export const authConfig = new AuthConfigManager()
|
|
145
|
+
export default authConfig
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sync Module - Cloud synchronization for prjct-cli
|
|
3
|
+
*
|
|
4
|
+
* Provides:
|
|
5
|
+
* - AuthConfig: API key storage and management
|
|
6
|
+
* - SyncClient: HTTP client for prjct API
|
|
7
|
+
* - SyncManager: Orchestrates push/pull operations
|
|
8
|
+
* - OAuthHandler: Authentication flow management
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
// Auth
|
|
12
|
+
export { authConfig, type AuthConfig } from './auth-config'
|
|
13
|
+
|
|
14
|
+
// OAuth
|
|
15
|
+
export { oauthHandler, type AuthResult } from './oauth-handler'
|
|
16
|
+
|
|
17
|
+
// Client
|
|
18
|
+
export {
|
|
19
|
+
syncClient,
|
|
20
|
+
type SyncBatchResult,
|
|
21
|
+
type SyncPullResult,
|
|
22
|
+
type SyncStatus,
|
|
23
|
+
type SyncClientError,
|
|
24
|
+
} from './sync-client'
|
|
25
|
+
|
|
26
|
+
// Manager
|
|
27
|
+
export { syncManager, type SyncResult, type PushResult, type PullResult } from './sync-manager'
|
|
28
|
+
|
|
29
|
+
// Default export is the main sync manager
|
|
30
|
+
export { syncManager as default } from './sync-manager'
|