prjct-cli 2.57.0 → 2.59.0
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 +10 -0
- package/README.md +20 -1
- package/bin/prjct.cjs +250 -0
- package/dist/bin/prjct-core.mjs +415 -404
- package/dist/bin/prjct.mjs +7 -4
- package/dist/daemon/entry.mjs +292 -292
- package/dist/mcp/server.mjs +4 -4
- package/package.json +3 -2
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [2.59.0] - 2026-06-22
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
- **Windows and Linux compatibility foundations.** Package-manager installs now use a portable Node launcher on Windows, the daemon uses named pipes on Windows and Unix sockets on macOS/Linux, Git auto-sync hooks no longer depend on Unix-only shell utilities, and CI smokes Ubuntu, macOS, and Windows.
|
|
9
|
+
|
|
10
|
+
## [2.58.0] - 2026-06-22
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- stale sync slash copy
|
|
14
|
+
|
|
5
15
|
## [2.57.0] - 2026-06-22
|
|
6
16
|
|
|
7
17
|
### Added
|
package/README.md
CHANGED
|
@@ -69,6 +69,24 @@ check, logs to `~/.prjct-cli/state/auto-update.log`).
|
|
|
69
69
|
|
|
70
70
|
Full install + upgrade paths: [INSTALL_PROMPT.md](./INSTALL_PROMPT.md).
|
|
71
71
|
|
|
72
|
+
### Platform support
|
|
73
|
+
|
|
74
|
+
The package-manager install path is the portable path for **macOS, Linux, and Windows**:
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
npm install -g prjct-cli@latest
|
|
78
|
+
# or pnpm / bun / yarn global install
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
The daemon uses Unix sockets on macOS/Linux and a Windows named pipe on Windows.
|
|
82
|
+
Git auto-sync hooks keep the shell wrapper POSIX-small and run rate limiting /
|
|
83
|
+
background spawn through Node, so they work in Git for Windows without relying on
|
|
84
|
+
Unix-only utilities like `md5sum`, `stat -f`, or `date +%s`.
|
|
85
|
+
|
|
86
|
+
The standalone `curl | bash` installer remains **macOS/Linux only**. Windows
|
|
87
|
+
users should use the package-manager install path. CI runs focused platform
|
|
88
|
+
compatibility smoke tests on Ubuntu, macOS, and Windows.
|
|
89
|
+
|
|
72
90
|
### Zero native dependencies
|
|
73
91
|
|
|
74
92
|
prjct-cli uses SQLite for local project memory through the runtime's **built-in**
|
|
@@ -449,7 +467,8 @@ Bring your own MCP — prjct-cli doesn't duplicate trackers.
|
|
|
449
467
|
|
|
450
468
|
```
|
|
451
469
|
prjct-cli/
|
|
452
|
-
bin/prjct
|
|
470
|
+
bin/prjct.cjs Portable package-manager launcher
|
|
471
|
+
dist/bin/prjct.mjs Thin JS shim (daemon-first)
|
|
453
472
|
core/
|
|
454
473
|
cli/ CLI command handlers + dispatcher
|
|
455
474
|
hooks/ 7 passive Claude Code hook subcommands
|
package/bin/prjct.cjs
ADDED
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Portable package-manager entry point for prjct.
|
|
5
|
+
*
|
|
6
|
+
* npm/pnpm/yarn/bun generate Windows shims correctly for node shebangs, but
|
|
7
|
+
* not for the historical bin/prjct POSIX shell launcher. Keep this file tiny
|
|
8
|
+
* and dependency-free so global installs work on macOS, Linux, and Windows.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const childProcess = require('node:child_process')
|
|
12
|
+
const fs = require('node:fs')
|
|
13
|
+
const os = require('node:os')
|
|
14
|
+
const path = require('node:path')
|
|
15
|
+
|
|
16
|
+
const SCRIPT_PATH = fs.realpathSync(__filename)
|
|
17
|
+
const SCRIPT_DIR = path.dirname(SCRIPT_PATH)
|
|
18
|
+
const ROOT_DIR = path.resolve(SCRIPT_DIR, '..')
|
|
19
|
+
const HOME = os.homedir()
|
|
20
|
+
|
|
21
|
+
function pathEntries() {
|
|
22
|
+
return (process.env.PATH || '').split(path.delimiter).filter(Boolean)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function executableCandidates(command) {
|
|
26
|
+
if (process.platform !== 'win32') return [command]
|
|
27
|
+
const pathext = (process.env.PATHEXT || '.COM;.EXE;.BAT;.CMD')
|
|
28
|
+
.split(';')
|
|
29
|
+
.filter(Boolean)
|
|
30
|
+
.map((ext) => ext.toLowerCase())
|
|
31
|
+
const ext = path.extname(command).toLowerCase()
|
|
32
|
+
if (ext && pathext.includes(ext)) return [command]
|
|
33
|
+
return pathext.map((candidateExt) => `${command}${candidateExt.toLowerCase()}`)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function findCommand(command) {
|
|
37
|
+
for (const dir of pathEntries()) {
|
|
38
|
+
for (const candidate of executableCandidates(command)) {
|
|
39
|
+
const fullPath = path.join(dir, candidate)
|
|
40
|
+
try {
|
|
41
|
+
fs.accessSync(fullPath, fs.constants.X_OK)
|
|
42
|
+
return fullPath
|
|
43
|
+
} catch {
|
|
44
|
+
/* keep searching */
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return null
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function nodeVersionOk() {
|
|
52
|
+
const match = process.versions.node.match(/^(\d+)\.(\d+)\./)
|
|
53
|
+
const major = Number.parseInt(match?.[1] || '0', 10)
|
|
54
|
+
const minor = Number.parseInt(match?.[2] || '0', 10)
|
|
55
|
+
return major > 22 || (major === 22 && minor >= 5)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function withSqliteFlag(env = process.env) {
|
|
59
|
+
const existing = env.NODE_OPTIONS || ''
|
|
60
|
+
const flag = '--experimental-sqlite'
|
|
61
|
+
return {
|
|
62
|
+
...env,
|
|
63
|
+
NODE_OPTIONS: existing.includes(flag) ? existing : `${flag}${existing ? ` ${existing}` : ''}`,
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function spawnAndExit(command, args, options = {}) {
|
|
68
|
+
const result = childProcess.spawnSync(command, args, {
|
|
69
|
+
stdio: 'inherit',
|
|
70
|
+
windowsHide: false,
|
|
71
|
+
...options,
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
if (result.error) {
|
|
75
|
+
console.error(`Error: ${result.error.message}`)
|
|
76
|
+
process.exit(1)
|
|
77
|
+
}
|
|
78
|
+
if (result.signal) {
|
|
79
|
+
process.kill(process.pid, result.signal)
|
|
80
|
+
}
|
|
81
|
+
process.exit(result.status ?? 1)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function removeIfExists(filePath) {
|
|
85
|
+
try {
|
|
86
|
+
if (fs.existsSync(filePath)) fs.rmSync(filePath, { force: true })
|
|
87
|
+
} catch {
|
|
88
|
+
/* best effort */
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function copyIfNewer(src, dest) {
|
|
93
|
+
try {
|
|
94
|
+
if (!fs.existsSync(src)) return
|
|
95
|
+
const srcStat = fs.statSync(src)
|
|
96
|
+
const destStat = fs.existsSync(dest) ? fs.statSync(dest) : null
|
|
97
|
+
if (destStat && destStat.mtimeMs >= srcStat.mtimeMs) return
|
|
98
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true })
|
|
99
|
+
fs.copyFileSync(src, dest)
|
|
100
|
+
} catch {
|
|
101
|
+
/* best effort */
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function copyDirContents(srcDir, destDir) {
|
|
106
|
+
try {
|
|
107
|
+
if (!fs.existsSync(srcDir)) return
|
|
108
|
+
fs.mkdirSync(destDir, { recursive: true })
|
|
109
|
+
for (const entry of fs.readdirSync(srcDir)) {
|
|
110
|
+
fs.copyFileSync(path.join(srcDir, entry), path.join(destDir, entry))
|
|
111
|
+
}
|
|
112
|
+
} catch {
|
|
113
|
+
/* best effort */
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function ensureSetup() {
|
|
118
|
+
removeIfExists(path.join(HOME, '.claude', 'commands', 'p.md'))
|
|
119
|
+
removeIfExists(path.join(HOME, '.gemini', 'commands', 'p.toml'))
|
|
120
|
+
|
|
121
|
+
const statuslineSrc = path.join(ROOT_DIR, 'assets', 'statusline', 'statusline.sh')
|
|
122
|
+
const statuslineDir = path.join(HOME, '.prjct-cli', 'statusline')
|
|
123
|
+
const statuslineDest = path.join(statuslineDir, 'statusline.sh')
|
|
124
|
+
const claudeStatusline = path.join(HOME, '.claude', 'prjct-statusline.sh')
|
|
125
|
+
|
|
126
|
+
try {
|
|
127
|
+
if (fs.existsSync(statuslineSrc)) {
|
|
128
|
+
const needsCopy =
|
|
129
|
+
!fs.existsSync(statuslineDest) ||
|
|
130
|
+
fs.statSync(statuslineSrc).mtimeMs > fs.statSync(statuslineDest).mtimeMs
|
|
131
|
+
if (needsCopy) {
|
|
132
|
+
fs.mkdirSync(statuslineDir, { recursive: true })
|
|
133
|
+
fs.copyFileSync(statuslineSrc, statuslineDest)
|
|
134
|
+
fs.chmodSync(statuslineDest, 0o755)
|
|
135
|
+
|
|
136
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(ROOT_DIR, 'package.json'), 'utf-8'))
|
|
137
|
+
if (pkg.version) {
|
|
138
|
+
const patched = fs
|
|
139
|
+
.readFileSync(statuslineDest, 'utf-8')
|
|
140
|
+
.replace(/CLI_VERSION="[^"]*"/, `CLI_VERSION="${pkg.version}"`)
|
|
141
|
+
fs.writeFileSync(statuslineDest, patched)
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
for (const subdir of ['lib', 'components', 'themes']) {
|
|
145
|
+
copyDirContents(
|
|
146
|
+
path.join(ROOT_DIR, 'assets', 'statusline', subdir),
|
|
147
|
+
path.join(statuslineDir, subdir)
|
|
148
|
+
)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
fs.mkdirSync(path.dirname(claudeStatusline), { recursive: true })
|
|
152
|
+
removeIfExists(claudeStatusline)
|
|
153
|
+
try {
|
|
154
|
+
fs.symlinkSync(statuslineDest, claudeStatusline)
|
|
155
|
+
} catch {
|
|
156
|
+
fs.copyFileSync(statuslineDest, claudeStatusline)
|
|
157
|
+
}
|
|
158
|
+
fs.chmodSync(claudeStatusline, 0o755)
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
} catch {
|
|
162
|
+
/* best effort */
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
copyIfNewer(
|
|
166
|
+
path.join(ROOT_DIR, 'templates', 'skills', 'prjct', 'SKILL.md'),
|
|
167
|
+
path.join(HOME, '.claude', 'skills', 'prjct', 'SKILL.md')
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
if (fs.existsSync(path.join(HOME, '.codex')) || findCommand('codex')) {
|
|
171
|
+
copyIfNewer(
|
|
172
|
+
path.join(ROOT_DIR, 'templates', 'codex', 'SKILL.md'),
|
|
173
|
+
path.join(HOME, '.codex', 'skills', 'prjct', 'SKILL.md')
|
|
174
|
+
)
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function runMcpServer(args) {
|
|
179
|
+
const mcpServer = path.join(ROOT_DIR, 'dist', 'mcp', 'server.mjs')
|
|
180
|
+
if (!fs.existsSync(mcpServer)) {
|
|
181
|
+
console.error("Error: MCP server entry point not found. Run 'npm run build' first.")
|
|
182
|
+
process.exit(1)
|
|
183
|
+
}
|
|
184
|
+
if (!nodeVersionOk()) {
|
|
185
|
+
console.error(
|
|
186
|
+
`Error: prjct MCP needs Node >=22.5 (for node:sqlite) - found Node ${process.versions.node}.`
|
|
187
|
+
)
|
|
188
|
+
process.exit(1)
|
|
189
|
+
}
|
|
190
|
+
spawnAndExit(process.execPath, [mcpServer, ...args.slice(1)], { env: withSqliteFlag() })
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function runWithNode(args) {
|
|
194
|
+
const distBin = path.join(ROOT_DIR, 'dist', 'bin', 'prjct.mjs')
|
|
195
|
+
|
|
196
|
+
if (!fs.existsSync(distBin)) {
|
|
197
|
+
if (
|
|
198
|
+
fs.existsSync(path.join(ROOT_DIR, 'scripts', 'build.js')) &&
|
|
199
|
+
fs.existsSync(path.join(ROOT_DIR, 'core'))
|
|
200
|
+
) {
|
|
201
|
+
console.error('Building for Node.js (first run)...')
|
|
202
|
+
const build = childProcess.spawnSync(
|
|
203
|
+
process.execPath,
|
|
204
|
+
[path.join(ROOT_DIR, 'scripts', 'build.js')],
|
|
205
|
+
{
|
|
206
|
+
cwd: ROOT_DIR,
|
|
207
|
+
stdio: 'inherit',
|
|
208
|
+
}
|
|
209
|
+
)
|
|
210
|
+
if (build.status !== 0) {
|
|
211
|
+
console.error("Error: Build failed. Run 'npm run build' manually.")
|
|
212
|
+
process.exit(build.status ?? 1)
|
|
213
|
+
}
|
|
214
|
+
} else {
|
|
215
|
+
console.error("Error: Compiled output not found. Run 'npm run build' first.")
|
|
216
|
+
process.exit(1)
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (!nodeVersionOk()) {
|
|
221
|
+
console.error(
|
|
222
|
+
`Error: prjct needs Node >=22.5 (for node:sqlite) - found Node ${process.versions.node}.`
|
|
223
|
+
)
|
|
224
|
+
console.error(' Upgrade Node (https://nodejs.org) or install Bun (https://bun.sh).')
|
|
225
|
+
process.exit(1)
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
spawnAndExit(process.execPath, [distBin, ...args], { env: withSqliteFlag() })
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function runWithBun(args) {
|
|
232
|
+
if (process.platform === 'win32') return false
|
|
233
|
+
const distBin = path.join(ROOT_DIR, 'dist', 'bin', 'prjct.mjs')
|
|
234
|
+
const bun = findCommand('bun')
|
|
235
|
+
if (!bun || !fs.existsSync(distBin)) return false
|
|
236
|
+
spawnAndExit(bun, [distBin, ...args])
|
|
237
|
+
return true
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function main() {
|
|
241
|
+
const args = process.argv.slice(2)
|
|
242
|
+
if (args[0] === 'mcp-server') runMcpServer(args)
|
|
243
|
+
|
|
244
|
+
ensureSetup()
|
|
245
|
+
|
|
246
|
+
if (runWithBun(args)) return
|
|
247
|
+
runWithNode(args)
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
main()
|