prjct-cli 1.6.10 → 1.6.12
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/CHANGELOG.md +66 -1
- package/bin/prjct.ts +11 -2
- package/core/agentic/prompt-builder.ts +2 -2
- package/core/context-tools/imports-tool.ts +12 -9
- package/core/infrastructure/ai-provider.ts +17 -5
- package/core/infrastructure/command-installer.ts +10 -15
- package/core/infrastructure/setup.ts +9 -14
- package/core/utils/provider-cache.ts +49 -0
- package/core/utils/version.ts +3 -0
- package/dist/bin/prjct.mjs +1108 -1059
- package/dist/core/infrastructure/command-installer.js +140 -100
- package/dist/core/infrastructure/setup.js +260 -225
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,12 +1,77 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.6.12] - 2026-02-07
|
|
4
|
+
|
|
5
|
+
### Bug Fixes
|
|
6
|
+
|
|
7
|
+
- replace sync I/O in imports-tool hot path (PRJ-290) (#137)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
## [1.6.14] - 2026-02-07
|
|
11
|
+
|
|
12
|
+
### Bug Fixes
|
|
13
|
+
- **Replace sync I/O in imports-tool hot path (PRJ-290)**: Converted `tryResolve`/`resolveImport`/`extractImports` from sync `require('node:fs')` with `existsSync`+`statSync` to async `fs.stat()` from `node:fs/promises`. Also replaced repeated `getPackageRoot()` calls with the pre-resolved `PACKAGE_ROOT` constant in prompt-builder, command-installer, and setup modules.
|
|
14
|
+
|
|
15
|
+
### Implementation Details
|
|
16
|
+
The `imports-tool.ts` file had an inline `require('node:fs')` call inside `tryResolve()` that used `existsSync` and `statSync` in a loop — a true hot path during import analysis. Converted the entire chain (`tryResolve` → `resolveImport` → `extractImports`) to async, using the already-imported `fs` from `node:fs/promises`. `version.ts` was kept sync intentionally: esbuild CJS output (used for postinstall) doesn't support top-level await, and its I/O runs once at cold start with results cached.
|
|
17
|
+
|
|
18
|
+
### Learnings
|
|
19
|
+
- esbuild CJS format does not support top-level `await` — async module exports require ESM format
|
|
20
|
+
- `version.ts` cold-start I/O is negligible (runs once, cached) vs `imports-tool.ts` which resolves extensions in a loop per import
|
|
21
|
+
- Using pre-resolved `PACKAGE_ROOT` constant avoids repeated sync function calls across modules
|
|
22
|
+
|
|
23
|
+
### Test Plan
|
|
24
|
+
|
|
25
|
+
#### For QA
|
|
26
|
+
1. Run `prjct context imports <file>` — verify import resolution works correctly (resolves `.ts`, `.tsx`, `.js` extensions and `/index.ts` barrel imports)
|
|
27
|
+
2. Run `prjct sync` — verify command-installer and setup find templates via `PACKAGE_ROOT`
|
|
28
|
+
3. Run `bun run build` — verify all 5 build targets compile without errors
|
|
29
|
+
4. Verify no `fs.*Sync()` calls remain in `imports-tool.ts`
|
|
30
|
+
|
|
31
|
+
#### For Users
|
|
32
|
+
**What changed:** Import analysis is now fully async, eliminating sync file system calls in the hot path.
|
|
33
|
+
**How to use:** No changes needed — `prjct context imports` works the same way.
|
|
34
|
+
**Breaking changes:** None.
|
|
35
|
+
|
|
36
|
+
## [1.6.11] - 2026-02-07
|
|
37
|
+
|
|
38
|
+
### Performance
|
|
39
|
+
|
|
40
|
+
- cache provider detection to eliminate redundant shell spawns (PRJ-289) (#136)
|
|
41
|
+
|
|
42
|
+
## [1.6.13] - 2026-02-07
|
|
43
|
+
|
|
44
|
+
### Improvements
|
|
45
|
+
- **Cache provider detection to eliminate redundant shell spawns (PRJ-289)**: Provider detection results (Claude, Gemini CLI availability) are now cached to `~/.prjct-cli/cache/providers.json` with a 10-minute TTL. Subsequent CLI commands skip shell spawns entirely. Added 2-second timeout on `which`/`--version` spawns to prevent hangs. Added `--refresh` flag to force re-detection.
|
|
46
|
+
|
|
47
|
+
### Implementation Details
|
|
48
|
+
Created `core/utils/provider-cache.ts` with `readProviderCache()`, `writeProviderCache()`, and `invalidateProviderCache()`. Wired into `detectAllProviders()` — checks cache first, falls through to shell detection on miss or expiry, writes cache after detection. `bin/prjct.ts` parses `--refresh` early (like `--quiet`), invalidates cache, and passes refresh flag to detection.
|
|
49
|
+
|
|
50
|
+
### Learnings
|
|
51
|
+
- `execAsync` accepts a `timeout` option (milliseconds) that kills the child process on expiry — ideal for preventing hangs on broken CLI installations.
|
|
52
|
+
- Biome enforces `Array#indexOf()` over `Array#findIndex()` for simple equality checks (`useIndexOf` rule).
|
|
53
|
+
- Separating cache logic into its own module keeps `ai-provider.ts` focused on detection logic.
|
|
54
|
+
|
|
55
|
+
### Test Plan
|
|
56
|
+
|
|
57
|
+
#### For QA
|
|
58
|
+
1. Run `prjct --version` twice — second run should be near-instant (cache hit)
|
|
59
|
+
2. Delete `~/.prjct-cli/cache/providers.json`, run `prjct --version` — should re-detect and recreate cache
|
|
60
|
+
3. Run `prjct --version --refresh` — should take ~2s (forced re-detection)
|
|
61
|
+
4. Edit cache file to set timestamp 11 minutes ago — next command should re-detect (TTL expired)
|
|
62
|
+
5. Run `prjct sync` — should use cached providers, no shell spawns
|
|
63
|
+
|
|
64
|
+
#### For Users
|
|
65
|
+
**What changed:** Provider detection is now cached for 10 minutes. CLI startup is ~30x faster for cached commands (~66ms vs ~2100ms).
|
|
66
|
+
**How to use:** Automatic. Use `--refresh` to force re-detection after installing a new CLI.
|
|
67
|
+
**Breaking changes:** None.
|
|
68
|
+
|
|
3
69
|
## [1.6.10] - 2026-02-07
|
|
4
70
|
|
|
5
71
|
### Bug Fixes
|
|
6
72
|
|
|
7
73
|
- resolve signal handler and EventBus listener accumulation leaks (PRJ-287) (#135)
|
|
8
74
|
|
|
9
|
-
|
|
10
75
|
## [1.6.12] - 2026-02-07
|
|
11
76
|
|
|
12
77
|
### Bug Fixes
|
package/bin/prjct.ts
CHANGED
|
@@ -16,6 +16,7 @@ import configManager from '../core/infrastructure/config-manager'
|
|
|
16
16
|
import editorsConfig from '../core/infrastructure/editors-config'
|
|
17
17
|
import { DEFAULT_PORT, startServer } from '../core/server/server'
|
|
18
18
|
import { fileExists } from '../core/utils/fs-helpers'
|
|
19
|
+
import { invalidateProviderCache } from '../core/utils/provider-cache'
|
|
19
20
|
import { VERSION } from '../core/utils/version'
|
|
20
21
|
|
|
21
22
|
/**
|
|
@@ -62,6 +63,14 @@ if (isQuietMode) {
|
|
|
62
63
|
setQuietMode(true)
|
|
63
64
|
}
|
|
64
65
|
|
|
66
|
+
// Parse --refresh flag (force re-detection of providers, invalidate cache)
|
|
67
|
+
const refreshIndex = args.indexOf('--refresh')
|
|
68
|
+
const isRefresh = refreshIndex !== -1
|
|
69
|
+
if (isRefresh) {
|
|
70
|
+
args.splice(refreshIndex, 1)
|
|
71
|
+
await invalidateProviderCache()
|
|
72
|
+
}
|
|
73
|
+
|
|
65
74
|
// Colors for output (chalk respects NO_COLOR env)
|
|
66
75
|
|
|
67
76
|
// Session tracking for commands that bypass core/index.ts
|
|
@@ -217,8 +226,8 @@ if (args[0] === 'start' || args[0] === 'setup') {
|
|
|
217
226
|
console.log(getHelp(topic))
|
|
218
227
|
process.exitCode = 0
|
|
219
228
|
} else if (args[0] === 'version' || args[0] === '-v' || args[0] === '--version') {
|
|
220
|
-
// Show version with provider status
|
|
221
|
-
const detection = await detectAllProviders()
|
|
229
|
+
// Show version with provider status (uses cached detection unless --refresh)
|
|
230
|
+
const detection = await detectAllProviders(isRefresh)
|
|
222
231
|
const home = os.homedir()
|
|
223
232
|
const cwd = process.cwd()
|
|
224
233
|
const [
|
|
@@ -27,7 +27,7 @@ import type {
|
|
|
27
27
|
} from '../types'
|
|
28
28
|
import { getErrorMessage, isNotFoundError } from '../types/fs'
|
|
29
29
|
import { fileExists } from '../utils/fs-helpers'
|
|
30
|
-
import {
|
|
30
|
+
import { PACKAGE_ROOT } from '../utils/version'
|
|
31
31
|
|
|
32
32
|
// Re-export types for convenience
|
|
33
33
|
export type {
|
|
@@ -132,7 +132,7 @@ class PromptBuilder {
|
|
|
132
132
|
* These modules extend the base global CLAUDE.md for complex operations
|
|
133
133
|
*/
|
|
134
134
|
async loadModule(moduleName: string): Promise<string | null> {
|
|
135
|
-
const modulePath = path.join(
|
|
135
|
+
const modulePath = path.join(PACKAGE_ROOT, 'templates/global/modules', moduleName)
|
|
136
136
|
return this.getTemplate(modulePath)
|
|
137
137
|
}
|
|
138
138
|
|
|
@@ -178,7 +178,7 @@ export async function analyzeImports(
|
|
|
178
178
|
const patterns = IMPORT_PATTERNS[language] || []
|
|
179
179
|
|
|
180
180
|
// Extract imports
|
|
181
|
-
const imports = extractImports(content, patterns, absolutePath, projectPath)
|
|
181
|
+
const imports = await extractImports(content, patterns, absolutePath, projectPath)
|
|
182
182
|
|
|
183
183
|
// Get reverse imports if requested
|
|
184
184
|
let importedBy: ImportedBy[] = []
|
|
@@ -217,12 +217,12 @@ export async function analyzeImports(
|
|
|
217
217
|
/**
|
|
218
218
|
* Extract imports from file content
|
|
219
219
|
*/
|
|
220
|
-
function extractImports(
|
|
220
|
+
async function extractImports(
|
|
221
221
|
content: string,
|
|
222
222
|
patterns: ImportPattern[],
|
|
223
223
|
absolutePath: string,
|
|
224
224
|
projectPath: string
|
|
225
|
-
): ImportRelation[] {
|
|
225
|
+
): Promise<ImportRelation[]> {
|
|
226
226
|
const imports: ImportRelation[] = []
|
|
227
227
|
const seen = new Set<string>()
|
|
228
228
|
|
|
@@ -254,7 +254,7 @@ function extractImports(
|
|
|
254
254
|
// Resolve internal imports
|
|
255
255
|
let resolved: string | null = null
|
|
256
256
|
if (!isExternal) {
|
|
257
|
-
resolved = resolveImport(source, absolutePath, projectPath)
|
|
257
|
+
resolved = await resolveImport(source, absolutePath, projectPath)
|
|
258
258
|
}
|
|
259
259
|
|
|
260
260
|
imports.push({
|
|
@@ -274,7 +274,11 @@ function extractImports(
|
|
|
274
274
|
/**
|
|
275
275
|
* Resolve a relative import to an absolute path
|
|
276
276
|
*/
|
|
277
|
-
function resolveImport(
|
|
277
|
+
async function resolveImport(
|
|
278
|
+
source: string,
|
|
279
|
+
fromFile: string,
|
|
280
|
+
projectPath: string
|
|
281
|
+
): Promise<string | null> {
|
|
278
282
|
const fileDir = path.dirname(fromFile)
|
|
279
283
|
|
|
280
284
|
// Handle path alias like @/
|
|
@@ -291,15 +295,14 @@ function resolveImport(source: string, fromFile: string, projectPath: string): s
|
|
|
291
295
|
/**
|
|
292
296
|
* Try to resolve a path, adding extensions if needed
|
|
293
297
|
*/
|
|
294
|
-
function tryResolve(basePath: string, projectPath: string): string | null {
|
|
298
|
+
async function tryResolve(basePath: string, projectPath: string): Promise<string | null> {
|
|
295
299
|
const extensions = ['', '.ts', '.tsx', '.js', '.jsx', '/index.ts', '/index.js']
|
|
296
300
|
|
|
297
301
|
for (const ext of extensions) {
|
|
298
302
|
const fullPath = basePath + ext
|
|
299
303
|
try {
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
if (fs.existsSync(fullPath) && fs.statSync(fullPath).isFile()) {
|
|
304
|
+
const stat = await fs.stat(fullPath)
|
|
305
|
+
if (stat.isFile()) {
|
|
303
306
|
return path.relative(projectPath, fullPath)
|
|
304
307
|
}
|
|
305
308
|
} catch {}
|
|
@@ -22,8 +22,10 @@ import os from 'node:os'
|
|
|
22
22
|
import path from 'node:path'
|
|
23
23
|
import { promisify } from 'node:util'
|
|
24
24
|
import { fileExists } from '../utils/fs-helpers'
|
|
25
|
+
import { readProviderCache, writeProviderCache } from '../utils/provider-cache'
|
|
25
26
|
|
|
26
27
|
const execAsync = promisify(exec)
|
|
28
|
+
const SPAWN_TIMEOUT_MS = 2000
|
|
27
29
|
|
|
28
30
|
import type {
|
|
29
31
|
AIProviderConfig,
|
|
@@ -179,7 +181,7 @@ export const Providers: Record<AIProviderName, AIProviderConfig> = {
|
|
|
179
181
|
*/
|
|
180
182
|
async function whichCommand(command: string): Promise<string | null> {
|
|
181
183
|
try {
|
|
182
|
-
const { stdout } = await execAsync(`which ${command}
|
|
184
|
+
const { stdout } = await execAsync(`which ${command}`, { timeout: SPAWN_TIMEOUT_MS })
|
|
183
185
|
return stdout.trim()
|
|
184
186
|
} catch {
|
|
185
187
|
return null
|
|
@@ -191,7 +193,7 @@ async function whichCommand(command: string): Promise<string | null> {
|
|
|
191
193
|
*/
|
|
192
194
|
async function getCliVersion(command: string): Promise<string | null> {
|
|
193
195
|
try {
|
|
194
|
-
const { stdout } = await execAsync(`${command} --version
|
|
196
|
+
const { stdout } = await execAsync(`${command} --version`, { timeout: SPAWN_TIMEOUT_MS })
|
|
195
197
|
// Extract version number from output (e.g., "claude 1.0.0" -> "1.0.0")
|
|
196
198
|
const match = stdout.match(/\d+\.\d+\.\d+/)
|
|
197
199
|
return match ? match[0] : stdout.trim()
|
|
@@ -229,14 +231,24 @@ export async function detectProvider(provider: AIProviderName): Promise<Provider
|
|
|
229
231
|
|
|
230
232
|
/**
|
|
231
233
|
* Detect all available CLI-based providers
|
|
232
|
-
*
|
|
234
|
+
* Results are cached to disk with a 10-minute TTL to avoid redundant shell spawns.
|
|
235
|
+
* Pass refresh=true to force re-detection.
|
|
233
236
|
*/
|
|
234
|
-
export async function detectAllProviders(): Promise<{
|
|
237
|
+
export async function detectAllProviders(refresh = false): Promise<{
|
|
235
238
|
claude: ProviderDetectionResult
|
|
236
239
|
gemini: ProviderDetectionResult
|
|
237
240
|
}> {
|
|
241
|
+
if (!refresh) {
|
|
242
|
+
const cached = await readProviderCache()
|
|
243
|
+
if (cached) return cached
|
|
244
|
+
}
|
|
245
|
+
|
|
238
246
|
const [claude, gemini] = await Promise.all([detectProvider('claude'), detectProvider('gemini')])
|
|
239
|
-
|
|
247
|
+
const detection = { claude, gemini }
|
|
248
|
+
|
|
249
|
+
await writeProviderCache(detection).catch(() => {})
|
|
250
|
+
|
|
251
|
+
return detection
|
|
240
252
|
}
|
|
241
253
|
|
|
242
254
|
/**
|
|
@@ -23,7 +23,7 @@ import type {
|
|
|
23
23
|
UninstallResult,
|
|
24
24
|
} from '../types'
|
|
25
25
|
import { getErrorMessage, isNotFoundError } from '../types/fs'
|
|
26
|
-
import {
|
|
26
|
+
import { PACKAGE_ROOT } from '../utils/version'
|
|
27
27
|
|
|
28
28
|
// =============================================================================
|
|
29
29
|
// Module Types
|
|
@@ -51,7 +51,7 @@ interface ModuleConfig {
|
|
|
51
51
|
*/
|
|
52
52
|
async function loadModuleConfig(): Promise<ModuleConfig | null> {
|
|
53
53
|
try {
|
|
54
|
-
const configPath = path.join(
|
|
54
|
+
const configPath = path.join(PACKAGE_ROOT, 'templates/global/modules/module-config.json')
|
|
55
55
|
const content = await fs.readFile(configPath, 'utf-8')
|
|
56
56
|
return JSON.parse(content) as ModuleConfig
|
|
57
57
|
} catch {
|
|
@@ -66,11 +66,11 @@ async function loadModuleConfig(): Promise<ModuleConfig | null> {
|
|
|
66
66
|
*/
|
|
67
67
|
export async function composeGlobalTemplate(profile?: string): Promise<string> {
|
|
68
68
|
const config = await loadModuleConfig()
|
|
69
|
-
const modulesDir = path.join(
|
|
69
|
+
const modulesDir = path.join(PACKAGE_ROOT, 'templates/global/modules')
|
|
70
70
|
|
|
71
71
|
// Fallback to legacy template if config not found
|
|
72
72
|
if (!config) {
|
|
73
|
-
const legacyPath = path.join(
|
|
73
|
+
const legacyPath = path.join(PACKAGE_ROOT, 'templates/global/CLAUDE.md')
|
|
74
74
|
return fs.readFile(legacyPath, 'utf-8')
|
|
75
75
|
}
|
|
76
76
|
|
|
@@ -81,7 +81,7 @@ export async function composeGlobalTemplate(profile?: string): Promise<string> {
|
|
|
81
81
|
// Fallback to default profile
|
|
82
82
|
const defaultProfile = config.profiles[config.default]
|
|
83
83
|
if (!defaultProfile) {
|
|
84
|
-
const legacyPath = path.join(
|
|
84
|
+
const legacyPath = path.join(PACKAGE_ROOT, 'templates/global/CLAUDE.md')
|
|
85
85
|
return fs.readFile(legacyPath, 'utf-8')
|
|
86
86
|
}
|
|
87
87
|
}
|
|
@@ -130,7 +130,7 @@ export async function getProfileForCommand(command: string): Promise<string> {
|
|
|
130
130
|
export async function installDocs(): Promise<{ success: boolean; error?: string }> {
|
|
131
131
|
try {
|
|
132
132
|
const docsDir = path.join(os.homedir(), '.prjct-cli', 'docs')
|
|
133
|
-
const templateDocsDir = path.join(
|
|
133
|
+
const templateDocsDir = path.join(PACKAGE_ROOT, 'templates/global/docs')
|
|
134
134
|
|
|
135
135
|
// Ensure docs directory exists
|
|
136
136
|
await fs.mkdir(docsDir, { recursive: true })
|
|
@@ -177,12 +177,7 @@ export async function installGlobalConfig(): Promise<GlobalConfigResult> {
|
|
|
177
177
|
await fs.mkdir(activeProvider.configDir, { recursive: true })
|
|
178
178
|
|
|
179
179
|
const globalConfigPath = path.join(activeProvider.configDir, activeProvider.contextFile)
|
|
180
|
-
const templatePath = path.join(
|
|
181
|
-
getPackageRoot(),
|
|
182
|
-
'templates',
|
|
183
|
-
'global',
|
|
184
|
-
activeProvider.contextFile
|
|
185
|
-
)
|
|
180
|
+
const templatePath = path.join(PACKAGE_ROOT, 'templates', 'global', activeProvider.contextFile)
|
|
186
181
|
|
|
187
182
|
// Read template content - use modular composition (PRJ-94)
|
|
188
183
|
let templateContent = ''
|
|
@@ -196,12 +191,12 @@ export async function installGlobalConfig(): Promise<GlobalConfigResult> {
|
|
|
196
191
|
templateContent = await composeGlobalTemplate('standard')
|
|
197
192
|
} catch {
|
|
198
193
|
// Final fallback to legacy template
|
|
199
|
-
const fallbackTemplatePath = path.join(
|
|
194
|
+
const fallbackTemplatePath = path.join(PACKAGE_ROOT, 'templates/global/CLAUDE.md')
|
|
200
195
|
templateContent = await fs.readFile(fallbackTemplatePath, 'utf-8')
|
|
201
196
|
}
|
|
202
197
|
} else {
|
|
203
198
|
// Fallback for other providers
|
|
204
|
-
const fallbackTemplatePath = path.join(
|
|
199
|
+
const fallbackTemplatePath = path.join(PACKAGE_ROOT, 'templates/global/CLAUDE.md')
|
|
205
200
|
templateContent = await fs.readFile(fallbackTemplatePath, 'utf-8')
|
|
206
201
|
// If it is Gemini, we should rename Claude to Gemini in the fallback content
|
|
207
202
|
if (providerName === 'gemini') {
|
|
@@ -296,7 +291,7 @@ export class CommandInstaller {
|
|
|
296
291
|
|
|
297
292
|
constructor() {
|
|
298
293
|
this.homeDir = os.homedir()
|
|
299
|
-
this.templatesDir = path.join(
|
|
294
|
+
this.templatesDir = path.join(PACKAGE_ROOT, 'templates', 'commands')
|
|
300
295
|
}
|
|
301
296
|
|
|
302
297
|
private async ensureInit(): Promise<void> {
|
|
@@ -28,7 +28,7 @@ import { getErrorMessage, isNotFoundError } from '../types/fs'
|
|
|
28
28
|
import type { AIProviderConfig, AIProviderName } from '../types/provider'
|
|
29
29
|
import { fileExists } from '../utils/fs-helpers'
|
|
30
30
|
import log from '../utils/logger'
|
|
31
|
-
import {
|
|
31
|
+
import { PACKAGE_ROOT, VERSION } from '../utils/version'
|
|
32
32
|
import {
|
|
33
33
|
detectAllProviders,
|
|
34
34
|
detectAntigravity,
|
|
@@ -255,7 +255,7 @@ export async function run(): Promise<SetupResults> {
|
|
|
255
255
|
async function installGeminiRouter(): Promise<boolean> {
|
|
256
256
|
try {
|
|
257
257
|
const geminiCommandsDir = path.join(os.homedir(), '.gemini', 'commands')
|
|
258
|
-
const routerSource = path.join(
|
|
258
|
+
const routerSource = path.join(PACKAGE_ROOT, 'templates', 'commands', 'p.toml')
|
|
259
259
|
const routerDest = path.join(geminiCommandsDir, 'p.toml')
|
|
260
260
|
|
|
261
261
|
// Ensure commands directory exists
|
|
@@ -280,7 +280,7 @@ async function installGeminiGlobalConfig(): Promise<{ success: boolean; action:
|
|
|
280
280
|
try {
|
|
281
281
|
const geminiDir = path.join(os.homedir(), '.gemini')
|
|
282
282
|
const globalConfigPath = path.join(geminiDir, 'GEMINI.md')
|
|
283
|
-
const templatePath = path.join(
|
|
283
|
+
const templatePath = path.join(PACKAGE_ROOT, 'templates', 'global', 'GEMINI.md')
|
|
284
284
|
|
|
285
285
|
// Ensure ~/.gemini directory exists
|
|
286
286
|
await fs.mkdir(geminiDir, { recursive: true })
|
|
@@ -361,7 +361,7 @@ export async function installAntigravitySkill(): Promise<{
|
|
|
361
361
|
const antigravitySkillsDir = path.join(os.homedir(), '.gemini', 'antigravity', 'skills')
|
|
362
362
|
const prjctSkillDir = path.join(antigravitySkillsDir, 'prjct')
|
|
363
363
|
const skillMdPath = path.join(prjctSkillDir, 'SKILL.md')
|
|
364
|
-
const templatePath = path.join(
|
|
364
|
+
const templatePath = path.join(PACKAGE_ROOT, 'templates', 'antigravity', 'SKILL.md')
|
|
365
365
|
|
|
366
366
|
// Ensure skills directory exists
|
|
367
367
|
await fs.mkdir(prjctSkillDir, { recursive: true })
|
|
@@ -431,8 +431,8 @@ export async function installCursorProject(projectRoot: string): Promise<{
|
|
|
431
431
|
|
|
432
432
|
const routerMdcDest = path.join(rulesDir, 'prjct.mdc')
|
|
433
433
|
|
|
434
|
-
const routerMdcSource = path.join(
|
|
435
|
-
const cursorCommandsSource = path.join(
|
|
434
|
+
const routerMdcSource = path.join(PACKAGE_ROOT, 'templates', 'cursor', 'router.mdc')
|
|
435
|
+
const cursorCommandsSource = path.join(PACKAGE_ROOT, 'templates', 'cursor', 'commands')
|
|
436
436
|
|
|
437
437
|
// Ensure directories exist
|
|
438
438
|
await fs.mkdir(rulesDir, { recursive: true })
|
|
@@ -574,13 +574,8 @@ export async function installWindsurfProject(projectRoot: string): Promise<{
|
|
|
574
574
|
|
|
575
575
|
const routerDest = path.join(rulesDir, 'prjct.md')
|
|
576
576
|
|
|
577
|
-
const routerSource = path.join(
|
|
578
|
-
const windsurfWorkflowsSource = path.join(
|
|
579
|
-
getPackageRoot(),
|
|
580
|
-
'templates',
|
|
581
|
-
'windsurf',
|
|
582
|
-
'workflows'
|
|
583
|
-
)
|
|
577
|
+
const routerSource = path.join(PACKAGE_ROOT, 'templates', 'windsurf', 'router.md')
|
|
578
|
+
const windsurfWorkflowsSource = path.join(PACKAGE_ROOT, 'templates', 'windsurf', 'workflows')
|
|
584
579
|
|
|
585
580
|
// Ensure directories exist
|
|
586
581
|
await fs.mkdir(rulesDir, { recursive: true })
|
|
@@ -785,7 +780,7 @@ async function installStatusLine(): Promise<void> {
|
|
|
785
780
|
const prjctConfigPath = path.join(prjctStatusLineDir, 'config.json')
|
|
786
781
|
|
|
787
782
|
// Source assets (from the package)
|
|
788
|
-
const assetsDir = path.join(
|
|
783
|
+
const assetsDir = path.join(PACKAGE_ROOT, 'assets', 'statusline')
|
|
789
784
|
const sourceScript = path.join(assetsDir, 'statusline.sh')
|
|
790
785
|
const sourceThemeDir = path.join(assetsDir, 'themes')
|
|
791
786
|
const sourceLibDir = path.join(assetsDir, 'lib')
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import fs from 'node:fs/promises'
|
|
2
|
+
import os from 'node:os'
|
|
3
|
+
import path from 'node:path'
|
|
4
|
+
import type { ProviderDetectionResult } from '../types/provider'
|
|
5
|
+
|
|
6
|
+
const CACHE_DIR = path.join(os.homedir(), '.prjct-cli', 'cache')
|
|
7
|
+
const CACHE_FILE = path.join(CACHE_DIR, 'providers.json')
|
|
8
|
+
const TTL_MS = 10 * 60 * 1000 // 10 minutes
|
|
9
|
+
|
|
10
|
+
interface ProviderCache {
|
|
11
|
+
timestamp: string
|
|
12
|
+
detection: {
|
|
13
|
+
claude: ProviderDetectionResult
|
|
14
|
+
gemini: ProviderDetectionResult
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export async function readProviderCache(): Promise<ProviderCache['detection'] | null> {
|
|
19
|
+
try {
|
|
20
|
+
const raw = await fs.readFile(CACHE_FILE, 'utf-8')
|
|
21
|
+
const cache: ProviderCache = JSON.parse(raw)
|
|
22
|
+
|
|
23
|
+
if (!cache.timestamp || !cache.detection) return null
|
|
24
|
+
|
|
25
|
+
const age = Date.now() - new Date(cache.timestamp).getTime()
|
|
26
|
+
if (age > TTL_MS) return null
|
|
27
|
+
|
|
28
|
+
return cache.detection
|
|
29
|
+
} catch {
|
|
30
|
+
return null
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export async function writeProviderCache(detection: ProviderCache['detection']): Promise<void> {
|
|
35
|
+
const cache: ProviderCache = {
|
|
36
|
+
timestamp: new Date().toISOString(),
|
|
37
|
+
detection,
|
|
38
|
+
}
|
|
39
|
+
await fs.mkdir(CACHE_DIR, { recursive: true })
|
|
40
|
+
await fs.writeFile(CACHE_FILE, JSON.stringify(cache, null, 2))
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export async function invalidateProviderCache(): Promise<void> {
|
|
44
|
+
try {
|
|
45
|
+
await fs.unlink(CACHE_FILE)
|
|
46
|
+
} catch {
|
|
47
|
+
// File doesn't exist — fine
|
|
48
|
+
}
|
|
49
|
+
}
|
package/core/utils/version.ts
CHANGED
|
@@ -7,6 +7,9 @@ import { getErrorMessage } from '../types/fs'
|
|
|
7
7
|
*
|
|
8
8
|
* Reads version from package.json dynamically to ensure consistency
|
|
9
9
|
* across the entire application.
|
|
10
|
+
*
|
|
11
|
+
* Uses sync I/O intentionally: runs once at cold start, results cached.
|
|
12
|
+
* CJS build (postinstall) requires sync module-level exports.
|
|
10
13
|
*/
|
|
11
14
|
|
|
12
15
|
interface PackageJson {
|