prjct-cli 0.10.12 → 0.10.13
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/agentic/command-executor.js +45 -0
- package/core/commands.js +72 -0
- package/core/index.js +11 -0
- package/core/utils/branding.js +47 -0
- package/core/utils/output.js +19 -4
- package/package.json +1 -1
|
@@ -15,6 +15,9 @@
|
|
|
15
15
|
* Source: Claude Code, Devin, Augment Code patterns
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
|
+
const fs = require('fs')
|
|
19
|
+
const path = require('path')
|
|
20
|
+
const os = require('os')
|
|
18
21
|
const templateLoader = require('./template-loader')
|
|
19
22
|
const contextBuilder = require('./context-builder')
|
|
20
23
|
const promptBuilder = require('./prompt-builder')
|
|
@@ -32,6 +35,9 @@ const groundTruth = require('./ground-truth')
|
|
|
32
35
|
const thinkBlocks = require('./think-blocks')
|
|
33
36
|
const parallelTools = require('./parallel-tools')
|
|
34
37
|
const planMode = require('./plan-mode')
|
|
38
|
+
|
|
39
|
+
// Running file for status line integration
|
|
40
|
+
const RUNNING_FILE = path.join(os.homedir(), '.prjct-cli', '.running')
|
|
35
41
|
// P3.5, P3.6, P3.7: DELEGATED TO CLAUDE CODE
|
|
36
42
|
// - semantic-search → Claude Code has Grep/Glob with semantic understanding
|
|
37
43
|
// - code-intelligence → Claude Code has native LSP integration
|
|
@@ -44,16 +50,48 @@ class CommandExecutor {
|
|
|
44
50
|
this.contextEstimator = null
|
|
45
51
|
}
|
|
46
52
|
|
|
53
|
+
/**
|
|
54
|
+
* Signal that a command is running (for status line)
|
|
55
|
+
*/
|
|
56
|
+
signalStart(commandName) {
|
|
57
|
+
try {
|
|
58
|
+
const dir = path.dirname(RUNNING_FILE)
|
|
59
|
+
if (!fs.existsSync(dir)) {
|
|
60
|
+
fs.mkdirSync(dir, { recursive: true })
|
|
61
|
+
}
|
|
62
|
+
fs.writeFileSync(RUNNING_FILE, `/p:${commandName}`)
|
|
63
|
+
} catch {
|
|
64
|
+
// Silently ignore - status line is optional
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Signal that command has finished (for status line)
|
|
70
|
+
*/
|
|
71
|
+
signalEnd() {
|
|
72
|
+
try {
|
|
73
|
+
if (fs.existsSync(RUNNING_FILE)) {
|
|
74
|
+
fs.unlinkSync(RUNNING_FILE)
|
|
75
|
+
}
|
|
76
|
+
} catch {
|
|
77
|
+
// Silently ignore - status line is optional
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
47
81
|
/**
|
|
48
82
|
* Execute command with MANDATORY agent assignment
|
|
49
83
|
*/
|
|
50
84
|
async execute(commandName, params, projectPath) {
|
|
85
|
+
// Signal start for status line
|
|
86
|
+
this.signalStart(commandName)
|
|
87
|
+
|
|
51
88
|
// Context for loop detection
|
|
52
89
|
const loopContext = params.task || params.description || ''
|
|
53
90
|
|
|
54
91
|
// Check if we're in a loop BEFORE attempting
|
|
55
92
|
if (loopDetector.shouldEscalate(commandName, loopContext)) {
|
|
56
93
|
const escalation = loopDetector.getEscalationInfo(commandName, loopContext)
|
|
94
|
+
this.signalEnd()
|
|
57
95
|
return {
|
|
58
96
|
success: false,
|
|
59
97
|
error: escalation.message,
|
|
@@ -73,6 +111,7 @@ class CommandExecutor {
|
|
|
73
111
|
// 2.5. VALIDATE: Pre-flight checks with specific errors
|
|
74
112
|
const validation = await validate(commandName, metadataContext)
|
|
75
113
|
if (!validation.valid) {
|
|
114
|
+
this.signalEnd()
|
|
76
115
|
return {
|
|
77
116
|
success: false,
|
|
78
117
|
error: formatError(validation),
|
|
@@ -267,6 +306,9 @@ class CommandExecutor {
|
|
|
267
306
|
// Record successful attempt
|
|
268
307
|
loopDetector.recordSuccess(commandName, loopContext)
|
|
269
308
|
|
|
309
|
+
// Signal end for status line
|
|
310
|
+
this.signalEnd()
|
|
311
|
+
|
|
270
312
|
return {
|
|
271
313
|
success: true,
|
|
272
314
|
template,
|
|
@@ -334,6 +376,9 @@ class CommandExecutor {
|
|
|
334
376
|
// - Native LSP for code intelligence
|
|
335
377
|
}
|
|
336
378
|
} catch (error) {
|
|
379
|
+
// Signal end for status line
|
|
380
|
+
this.signalEnd()
|
|
381
|
+
|
|
337
382
|
// Record failed attempt for loop detection
|
|
338
383
|
const attemptInfo = loopDetector.recordAttempt(commandName, loopContext, {
|
|
339
384
|
success: false,
|
package/core/commands.js
CHANGED
|
@@ -1925,6 +1925,15 @@ Agent: ${agent} (${Math.round(confidence * 100)}% confidence)
|
|
|
1925
1925
|
console.log(`⚠️ ${configResult.error}`)
|
|
1926
1926
|
}
|
|
1927
1927
|
|
|
1928
|
+
// Install status line for Claude Code
|
|
1929
|
+
console.log('\n⚡ Installing status line...')
|
|
1930
|
+
const statusLineResult = await this.installStatusLine()
|
|
1931
|
+
if (statusLineResult.success) {
|
|
1932
|
+
console.log('✅ Status line configured')
|
|
1933
|
+
} else {
|
|
1934
|
+
console.log(`⚠️ ${statusLineResult.error}`)
|
|
1935
|
+
}
|
|
1936
|
+
|
|
1928
1937
|
console.log('\n🎉 Setup complete!\n')
|
|
1929
1938
|
|
|
1930
1939
|
// Show beautiful ASCII art
|
|
@@ -1936,6 +1945,69 @@ Agent: ${agent} (${Math.round(confidence * 100)}% confidence)
|
|
|
1936
1945
|
}
|
|
1937
1946
|
}
|
|
1938
1947
|
|
|
1948
|
+
/**
|
|
1949
|
+
* Install status line script and configure settings.json
|
|
1950
|
+
*/
|
|
1951
|
+
async installStatusLine() {
|
|
1952
|
+
const fs = require('fs')
|
|
1953
|
+
const path = require('path')
|
|
1954
|
+
const os = require('os')
|
|
1955
|
+
|
|
1956
|
+
try {
|
|
1957
|
+
const claudeDir = path.join(os.homedir(), '.claude')
|
|
1958
|
+
const settingsPath = path.join(claudeDir, 'settings.json')
|
|
1959
|
+
const statusLinePath = path.join(claudeDir, 'prjct-statusline.sh')
|
|
1960
|
+
|
|
1961
|
+
// Copy status line script
|
|
1962
|
+
const scriptContent = `#!/bin/bash
|
|
1963
|
+
# prjct Status Line for Claude Code
|
|
1964
|
+
# Shows ⚡ prjct with animated spinner when command is running
|
|
1965
|
+
|
|
1966
|
+
# Read JSON context from stdin (provided by Claude Code)
|
|
1967
|
+
read -r json
|
|
1968
|
+
|
|
1969
|
+
# Spinner frames
|
|
1970
|
+
frames=('⠋' '⠙' '⠹' '⠸' '⠼' '⠴' '⠦' '⠧' '⠇' '⠏')
|
|
1971
|
+
|
|
1972
|
+
# Calculate frame based on time (changes every 80ms)
|
|
1973
|
+
frame=$(($(date +%s%N 2>/dev/null || echo 0) / 80000000 % 10))
|
|
1974
|
+
|
|
1975
|
+
# Check if prjct command is running
|
|
1976
|
+
running_file="$HOME/.prjct-cli/.running"
|
|
1977
|
+
|
|
1978
|
+
if [ -f "$running_file" ]; then
|
|
1979
|
+
task=$(cat "$running_file" 2>/dev/null || echo "working")
|
|
1980
|
+
echo "⚡ prjct \${frames[$frame]} $task"
|
|
1981
|
+
else
|
|
1982
|
+
echo "⚡ prjct"
|
|
1983
|
+
fi
|
|
1984
|
+
`
|
|
1985
|
+
fs.writeFileSync(statusLinePath, scriptContent, { mode: 0o755 })
|
|
1986
|
+
|
|
1987
|
+
// Update settings.json
|
|
1988
|
+
let settings = {}
|
|
1989
|
+
if (fs.existsSync(settingsPath)) {
|
|
1990
|
+
try {
|
|
1991
|
+
settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'))
|
|
1992
|
+
} catch {
|
|
1993
|
+
// Invalid JSON, start fresh
|
|
1994
|
+
}
|
|
1995
|
+
}
|
|
1996
|
+
|
|
1997
|
+
// Set status line configuration
|
|
1998
|
+
settings.statusLine = {
|
|
1999
|
+
type: 'command',
|
|
2000
|
+
command: statusLinePath
|
|
2001
|
+
}
|
|
2002
|
+
|
|
2003
|
+
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2))
|
|
2004
|
+
|
|
2005
|
+
return { success: true }
|
|
2006
|
+
} catch (error) {
|
|
2007
|
+
return { success: false, error: error.message }
|
|
2008
|
+
}
|
|
2009
|
+
}
|
|
2010
|
+
|
|
1939
2011
|
/**
|
|
1940
2012
|
* Show beautiful ASCII art with quick start
|
|
1941
2013
|
*/
|
package/core/index.js
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
const { PrjctCommands } = require('./commands')
|
|
8
8
|
const registry = require('./command-registry')
|
|
9
|
+
const out = require('./utils/output')
|
|
9
10
|
|
|
10
11
|
async function main() {
|
|
11
12
|
const [commandName, ...rawArgs] = process.argv.slice(2)
|
|
@@ -25,6 +26,9 @@ async function main() {
|
|
|
25
26
|
|
|
26
27
|
// === DYNAMIC COMMAND EXECUTION ===
|
|
27
28
|
|
|
29
|
+
// Show branding header
|
|
30
|
+
out.start()
|
|
31
|
+
|
|
28
32
|
try {
|
|
29
33
|
// 1. Find command in registry
|
|
30
34
|
const cmd = registry.getByName(commandName)
|
|
@@ -32,6 +36,7 @@ async function main() {
|
|
|
32
36
|
if (!cmd) {
|
|
33
37
|
console.error(`Unknown command: ${commandName}`)
|
|
34
38
|
console.error(`\nUse 'prjct --help' to see available commands.`)
|
|
39
|
+
out.end()
|
|
35
40
|
process.exit(1)
|
|
36
41
|
}
|
|
37
42
|
|
|
@@ -41,6 +46,7 @@ async function main() {
|
|
|
41
46
|
if (cmd.replacedBy) {
|
|
42
47
|
console.error(`Use 'prjct ${cmd.replacedBy}' instead.`)
|
|
43
48
|
}
|
|
49
|
+
out.end()
|
|
44
50
|
process.exit(1)
|
|
45
51
|
}
|
|
46
52
|
|
|
@@ -49,6 +55,7 @@ async function main() {
|
|
|
49
55
|
console.error(`Command '${commandName}' exists but is not yet implemented.`)
|
|
50
56
|
console.error(`Check the roadmap or contribute: https://github.com/jlopezlira/prjct-cli`)
|
|
51
57
|
console.error(`\nUse 'prjct --help' to see available commands.`)
|
|
58
|
+
out.end()
|
|
52
59
|
process.exit(1)
|
|
53
60
|
}
|
|
54
61
|
|
|
@@ -90,12 +97,16 @@ async function main() {
|
|
|
90
97
|
console.log(result.message)
|
|
91
98
|
}
|
|
92
99
|
|
|
100
|
+
// Show branding footer
|
|
101
|
+
out.end()
|
|
93
102
|
process.exit(result && result.success ? 0 : 1)
|
|
94
103
|
} catch (error) {
|
|
95
104
|
console.error('Error:', error.message)
|
|
96
105
|
if (process.env.DEBUG) {
|
|
97
106
|
console.error(error.stack)
|
|
98
107
|
}
|
|
108
|
+
// Show branding footer even on error
|
|
109
|
+
out.end()
|
|
99
110
|
process.exit(1)
|
|
100
111
|
}
|
|
101
112
|
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Branding Configuration for prjct-cli
|
|
3
|
+
* Single source of truth for all branding across CLI and Claude Code
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const chalk = require('chalk')
|
|
7
|
+
|
|
8
|
+
const SPINNER_FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']
|
|
9
|
+
const SPINNER_SPEED = 80
|
|
10
|
+
|
|
11
|
+
const branding = {
|
|
12
|
+
// Core identity
|
|
13
|
+
name: 'prjct',
|
|
14
|
+
icon: '⚡',
|
|
15
|
+
signature: '⚡ prjct',
|
|
16
|
+
|
|
17
|
+
// Spinner config
|
|
18
|
+
spinner: {
|
|
19
|
+
frames: SPINNER_FRAMES,
|
|
20
|
+
speed: SPINNER_SPEED
|
|
21
|
+
},
|
|
22
|
+
|
|
23
|
+
// CLI output (with chalk colors)
|
|
24
|
+
cli: {
|
|
25
|
+
header: () => chalk.cyan.bold('⚡') + ' ' + chalk.cyan('prjct'),
|
|
26
|
+
footer: () => chalk.dim('⚡ prjct'),
|
|
27
|
+
spin: (frame, msg) => chalk.cyan('⚡') + ' ' + chalk.cyan('prjct') + ' ' + chalk.cyan(SPINNER_FRAMES[frame % 10]) + ' ' + chalk.dim(msg || '')
|
|
28
|
+
},
|
|
29
|
+
|
|
30
|
+
// Template/Claude (plain text)
|
|
31
|
+
template: {
|
|
32
|
+
header: '⚡ prjct',
|
|
33
|
+
footer: '⚡ prjct'
|
|
34
|
+
},
|
|
35
|
+
|
|
36
|
+
// Git commit footer
|
|
37
|
+
commitFooter: `🤖 Generated with [p/](https://www.prjct.app/)
|
|
38
|
+
Designed for [Claude](https://www.anthropic.com/claude)`,
|
|
39
|
+
|
|
40
|
+
// URLs
|
|
41
|
+
urls: {
|
|
42
|
+
website: 'https://prjct.app',
|
|
43
|
+
docs: 'https://prjct.app/docs'
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
module.exports = branding
|
package/core/utils/output.js
CHANGED
|
@@ -1,24 +1,39 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Minimal Output System for prjct-cli
|
|
3
3
|
* Spinner while working → Single line result
|
|
4
|
+
* With ⚡ prjct branding
|
|
4
5
|
*/
|
|
5
6
|
|
|
6
7
|
const chalk = require('chalk')
|
|
8
|
+
const branding = require('./branding')
|
|
7
9
|
|
|
8
|
-
const FRAMES =
|
|
9
|
-
const SPEED =
|
|
10
|
+
const FRAMES = branding.spinner.frames
|
|
11
|
+
const SPEED = branding.spinner.speed
|
|
10
12
|
|
|
11
13
|
let interval = null
|
|
12
14
|
let frame = 0
|
|
13
15
|
|
|
14
16
|
const truncate = (s, max = 50) => (s && s.length > max ? s.slice(0, max - 1) + '…' : s || '')
|
|
15
|
-
const clear = () => process.stdout.write('\r' + ' '.repeat(
|
|
17
|
+
const clear = () => process.stdout.write('\r' + ' '.repeat(80) + '\r')
|
|
16
18
|
|
|
17
19
|
const out = {
|
|
20
|
+
// Branding: Show header at start
|
|
21
|
+
start() {
|
|
22
|
+
console.log(branding.cli.header())
|
|
23
|
+
return this
|
|
24
|
+
},
|
|
25
|
+
|
|
26
|
+
// Branding: Show footer at end
|
|
27
|
+
end() {
|
|
28
|
+
console.log(branding.cli.footer())
|
|
29
|
+
return this
|
|
30
|
+
},
|
|
31
|
+
|
|
32
|
+
// Branded spinner: ⚡ prjct ⠋ message...
|
|
18
33
|
spin(msg) {
|
|
19
34
|
this.stop()
|
|
20
35
|
interval = setInterval(() => {
|
|
21
|
-
process.stdout.write(`\r${
|
|
36
|
+
process.stdout.write(`\r${branding.cli.spin(frame++, truncate(msg, 45))}`)
|
|
22
37
|
}, SPEED)
|
|
23
38
|
return this
|
|
24
39
|
},
|