100xprompt-cli 0.1.72
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/LICENSE +35 -0
- package/NOTICE +68 -0
- package/bin/100xprompt.js +69 -0
- package/package.json +41 -0
- package/postinstall.mjs +372 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
100xprompt
|
|
2
|
+
Copyright (c) 2025 100xprompt
|
|
3
|
+
|
|
4
|
+
All rights reserved, except as required by the upstream licence reproduced
|
|
5
|
+
below. 100xprompt is a derivative work of opencode. MIT permits sublicensing
|
|
6
|
+
under different terms provided the original copyright and permission notice are
|
|
7
|
+
retained — that is the purpose of the second section of this file. See NOTICE
|
|
8
|
+
for the full list of third-party components bundled into the binary.
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
================================================================================
|
|
12
|
+
UPSTREAM — opencode (https://github.com/sst/opencode)
|
|
13
|
+
================================================================================
|
|
14
|
+
|
|
15
|
+
MIT License
|
|
16
|
+
|
|
17
|
+
Copyright (c) 2025 opencode
|
|
18
|
+
|
|
19
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
20
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
21
|
+
in the Software without restriction, including without limitation the rights
|
|
22
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
23
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
24
|
+
furnished to do so, subject to the following conditions:
|
|
25
|
+
|
|
26
|
+
The above copyright notice and this permission notice shall be included in all
|
|
27
|
+
copies or substantial portions of the Software.
|
|
28
|
+
|
|
29
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
30
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
31
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
32
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
33
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
34
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
35
|
+
SOFTWARE.
|
package/NOTICE
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
NOTICE — third-party components distributed with 100xprompt
|
|
2
|
+
================================================================================
|
|
3
|
+
|
|
4
|
+
This file lists third-party material bundled into the 100xprompt binary or the
|
|
5
|
+
published npm packages. It is a companion to LICENSE, which carries the upstream
|
|
6
|
+
copyright notice for the opencode-derived portions of this software.
|
|
7
|
+
|
|
8
|
+
Where a licence is stated as "not verified", the entry is recorded from general
|
|
9
|
+
knowledge rather than from the upstream project's own licence file, and should
|
|
10
|
+
be confirmed before anyone relies on it. Recording an unverified attribution is
|
|
11
|
+
better than recording none; it is not a substitute for checking.
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
1. DERIVED FROM
|
|
15
|
+
--------------------------------------------------------------------------------
|
|
16
|
+
|
|
17
|
+
opencode — https://github.com/sst/opencode — MIT
|
|
18
|
+
100xprompt is a derivative work of opencode. Its copyright and permission
|
|
19
|
+
notice are reproduced in full in LICENSE.
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
2. TREE-SITTER GRAMMARS (compiled into the binary as build assets)
|
|
23
|
+
--------------------------------------------------------------------------------
|
|
24
|
+
|
|
25
|
+
The syntax highlighter embeds compiled tree-sitter grammars and their query
|
|
26
|
+
files. Each comes from a separate upstream project with its own licence:
|
|
27
|
+
|
|
28
|
+
bash, go, javascript, json, python, rust, tsx, typescript
|
|
29
|
+
tree-sitter grammar projects (github.com/tree-sitter/*)
|
|
30
|
+
markdown, markdown_inline
|
|
31
|
+
github.com/tree-sitter-grammars/tree-sitter-markdown
|
|
32
|
+
|
|
33
|
+
The `.scm` query files vendored alongside each grammar originate from those same
|
|
34
|
+
projects, except the markdown highlight queries, which are 100xprompt's own —
|
|
35
|
+
see packages/tui/scripts/queries/. The runtime is web-tree-sitter (MIT).
|
|
36
|
+
|
|
37
|
+
Regenerate with: bun run --cwd packages/tui vendor-grammars
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
3. TERMINAL COLOUR SCHEMES
|
|
41
|
+
--------------------------------------------------------------------------------
|
|
42
|
+
|
|
43
|
+
The bundled themes are ports of third-party colour schemes. The palettes are the
|
|
44
|
+
work of their original authors; only the mapping to our theme format is ours:
|
|
45
|
+
|
|
46
|
+
aura, ayu, catppuccin (frappe/macchiato), cobalt2, cursor, dracula,
|
|
47
|
+
everforest, flexoki, github, gruvbox, kanagawa, material, monokai,
|
|
48
|
+
nightowl, nord, one-dark, osaka-jade, palenight, rosepine, solarized,
|
|
49
|
+
synthwave84, tokyonight, vercel, vesper, zenburn
|
|
50
|
+
|
|
51
|
+
100xprompt, lucent-orng, matrix, mercury, orng — original to this project.
|
|
52
|
+
|
|
53
|
+
Two need attention before redistribution and are flagged here rather than
|
|
54
|
+
quietly shipped:
|
|
55
|
+
|
|
56
|
+
tokyonight Apache-2.0, NOT MIT. Apache-2.0 carries NOTICE-file and
|
|
57
|
+
change-statement obligations that a blanket permissive
|
|
58
|
+
assumption does not satisfy.
|
|
59
|
+
monokai The original TextMate theme was never clearly licensed and the
|
|
60
|
+
name is claimed as a trademark.
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
4. RUNTIME DEPENDENCIES
|
|
64
|
+
--------------------------------------------------------------------------------
|
|
65
|
+
|
|
66
|
+
Ordinary npm dependencies (react, react-reconciler, web-tree-sitter and others)
|
|
67
|
+
are not vendored. Their licences travel with their own packages in node_modules
|
|
68
|
+
and are not reproduced here.
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
const { spawn } = require('child_process')
|
|
3
|
+
const path = require('path')
|
|
4
|
+
const fs = require('fs')
|
|
5
|
+
|
|
6
|
+
function getPlatformPackageName() {
|
|
7
|
+
const platform = process.platform
|
|
8
|
+
const arch = process.arch
|
|
9
|
+
const isMusl = process.report?.getReport?.()?.header?.glibcVersionRuntime === undefined
|
|
10
|
+
|
|
11
|
+
let osName
|
|
12
|
+
if (platform === 'darwin') osName = 'darwin'
|
|
13
|
+
else if (platform === 'linux') osName = 'linux'
|
|
14
|
+
else if (platform === 'win32') osName = 'windows'
|
|
15
|
+
|
|
16
|
+
let pkgName = `@100xprompt/cli-${osName}-${arch}`
|
|
17
|
+
if (platform === 'linux' && isMusl) {
|
|
18
|
+
pkgName = `@100xprompt/cli-linux-${arch}-musl`
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return pkgName
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function findBinary() {
|
|
25
|
+
const pkgName = getPlatformPackageName()
|
|
26
|
+
const possiblePaths = [
|
|
27
|
+
path.join(__dirname, '..', '..', pkgName, 'bin', '100xprompt'),
|
|
28
|
+
path.join(__dirname, '..', '..', pkgName, 'bin', '100xprompt.exe'),
|
|
29
|
+
path.join(__dirname, '..', '..', 'node_modules', pkgName, 'bin', '100xprompt'),
|
|
30
|
+
path.join(__dirname, '..', '..', 'node_modules', pkgName, 'bin', '100xprompt.exe'),
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
for (const p of possiblePaths) {
|
|
34
|
+
if (fs.existsSync(p)) return p
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
try {
|
|
38
|
+
const pkgPath = require.resolve(pkgName + '/package.json')
|
|
39
|
+
const binPath = path.join(path.dirname(pkgPath), 'bin', '100xprompt')
|
|
40
|
+
if (fs.existsSync(binPath)) return binPath
|
|
41
|
+
if (fs.existsSync(binPath + '.exe')) return binPath + '.exe'
|
|
42
|
+
} catch {}
|
|
43
|
+
|
|
44
|
+
return null
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const binaryPath = findBinary()
|
|
48
|
+
if (!binaryPath) {
|
|
49
|
+
console.error(`Could not find binary for platform: ${getPlatformPackageName()}`)
|
|
50
|
+
process.exit(1)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const child = spawn(binaryPath, process.argv.slice(2), {
|
|
54
|
+
stdio: 'inherit',
|
|
55
|
+
env: process.env
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
child.on('error', (err) => {
|
|
59
|
+
console.error('[100XPrompt] Failed to start:', err.message)
|
|
60
|
+
process.exit(1)
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
child.on('exit', (code, signal) => {
|
|
64
|
+
if (signal) {
|
|
65
|
+
console.error(`[100XPrompt] Process was killed with signal: ${signal}`)
|
|
66
|
+
process.exit(1)
|
|
67
|
+
}
|
|
68
|
+
process.exit(code || 0)
|
|
69
|
+
})
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "100xprompt-cli",
|
|
3
|
+
"version": "0.1.72",
|
|
4
|
+
"description": "Multi-model terminal coding agent. Route work across Claude, GLM, Gemini, DeepSeek and local models from one harness.",
|
|
5
|
+
"license": "UNLICENSED",
|
|
6
|
+
"author": "100XPrompt",
|
|
7
|
+
"homepage": "https://100xprompt.com",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/nipurn123/100xprompt.git",
|
|
11
|
+
"directory": "packages/100xprompt"
|
|
12
|
+
},
|
|
13
|
+
"keywords": [
|
|
14
|
+
"ai",
|
|
15
|
+
"agent",
|
|
16
|
+
"cli",
|
|
17
|
+
"coding-agent",
|
|
18
|
+
"llm",
|
|
19
|
+
"tui",
|
|
20
|
+
"100xprompt"
|
|
21
|
+
],
|
|
22
|
+
"bin": {
|
|
23
|
+
"100xprompt": "bin/100xprompt.js"
|
|
24
|
+
},
|
|
25
|
+
"scripts": {
|
|
26
|
+
"postinstall": "bun ./postinstall.mjs || node ./postinstall.mjs"
|
|
27
|
+
},
|
|
28
|
+
"optionalDependencies": {
|
|
29
|
+
"@100xprompt/cli-linux-arm64": "0.1.72",
|
|
30
|
+
"@100xprompt/cli-linux-x64": "0.1.72",
|
|
31
|
+
"@100xprompt/cli-linux-x64-baseline": "0.1.72",
|
|
32
|
+
"@100xprompt/cli-linux-arm64-musl": "0.1.72",
|
|
33
|
+
"@100xprompt/cli-linux-x64-musl": "0.1.72",
|
|
34
|
+
"@100xprompt/cli-linux-x64-baseline-musl": "0.1.72",
|
|
35
|
+
"@100xprompt/cli-darwin-arm64": "0.1.72",
|
|
36
|
+
"@100xprompt/cli-darwin-x64": "0.1.72",
|
|
37
|
+
"@100xprompt/cli-darwin-x64-baseline": "0.1.72",
|
|
38
|
+
"@100xprompt/cli-windows-x64": "0.1.72",
|
|
39
|
+
"@100xprompt/cli-windows-x64-baseline": "0.1.72"
|
|
40
|
+
}
|
|
41
|
+
}
|
package/postinstall.mjs
ADDED
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import fs from "fs"
|
|
4
|
+
import path from "path"
|
|
5
|
+
import os from "os"
|
|
6
|
+
import { execFileSync } from "child_process"
|
|
7
|
+
import { fileURLToPath } from "url"
|
|
8
|
+
import { createRequire } from "module"
|
|
9
|
+
|
|
10
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
11
|
+
const require = createRequire(import.meta.url)
|
|
12
|
+
|
|
13
|
+
function detectPlatformAndArch() {
|
|
14
|
+
// Map platform names
|
|
15
|
+
let platform
|
|
16
|
+
switch (os.platform()) {
|
|
17
|
+
case "darwin":
|
|
18
|
+
platform = "darwin"
|
|
19
|
+
break
|
|
20
|
+
case "linux":
|
|
21
|
+
platform = "linux"
|
|
22
|
+
break
|
|
23
|
+
case "win32":
|
|
24
|
+
platform = "windows"
|
|
25
|
+
break
|
|
26
|
+
default:
|
|
27
|
+
platform = os.platform()
|
|
28
|
+
break
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Map architecture names
|
|
32
|
+
let arch
|
|
33
|
+
switch (os.arch()) {
|
|
34
|
+
case "x64":
|
|
35
|
+
arch = "x64"
|
|
36
|
+
break
|
|
37
|
+
case "arm64":
|
|
38
|
+
arch = "arm64"
|
|
39
|
+
break
|
|
40
|
+
case "arm":
|
|
41
|
+
arch = "arm"
|
|
42
|
+
break
|
|
43
|
+
default:
|
|
44
|
+
arch = os.arch()
|
|
45
|
+
break
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// glibc vs musl matters for the Linux build variant. When the runtime report
|
|
49
|
+
// exposes no glibc version, we're on a musl libc (Alpine, etc).
|
|
50
|
+
let isMusl = false
|
|
51
|
+
if (platform === "linux") {
|
|
52
|
+
try {
|
|
53
|
+
isMusl = process.report?.getReport?.()?.header?.glibcVersionRuntime === undefined
|
|
54
|
+
} catch {
|
|
55
|
+
isMusl = false
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return { platform, arch, isMusl }
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Candidate platform-package names, in preference order. Mirrors the runtime
|
|
63
|
+
// wrapper (bin/100xprompt.js) and also probes the `-baseline` variant as a
|
|
64
|
+
// fallback for older CPUs without AVX2.
|
|
65
|
+
function candidatePackageNames() {
|
|
66
|
+
const { platform, arch, isMusl } = detectPlatformAndArch()
|
|
67
|
+
const base = `@100xprompt/cli-${platform}-${arch}`
|
|
68
|
+
const names = []
|
|
69
|
+
if (platform === "linux" && isMusl) {
|
|
70
|
+
names.push(`${base}-musl`, `${base}-baseline-musl`)
|
|
71
|
+
}
|
|
72
|
+
names.push(base, `${base}-baseline`)
|
|
73
|
+
return names
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function findBinary() {
|
|
77
|
+
const { platform } = detectPlatformAndArch()
|
|
78
|
+
const binaryName = platform === "windows" ? "100xprompt.exe" : "100xprompt"
|
|
79
|
+
|
|
80
|
+
for (const packageName of candidatePackageNames()) {
|
|
81
|
+
// Probe common install layouts (sibling package, hoisted node_modules,
|
|
82
|
+
// and require.resolve) so we work under npm/bun/pnpm/yarn and global installs.
|
|
83
|
+
const candidates = []
|
|
84
|
+
try {
|
|
85
|
+
const packageJsonPath = require.resolve(`${packageName}/package.json`)
|
|
86
|
+
candidates.push(path.join(path.dirname(packageJsonPath), "bin", binaryName))
|
|
87
|
+
} catch {
|
|
88
|
+
/* not resolvable from here — fall through to path probing */
|
|
89
|
+
}
|
|
90
|
+
candidates.push(
|
|
91
|
+
path.join(__dirname, "..", packageName, "bin", binaryName),
|
|
92
|
+
path.join(__dirname, "node_modules", packageName, "bin", binaryName),
|
|
93
|
+
path.join(__dirname, "..", "node_modules", packageName, "bin", binaryName),
|
|
94
|
+
path.join(__dirname, "..", "..", packageName, "bin", binaryName),
|
|
95
|
+
path.join(__dirname, "..", "..", "node_modules", packageName, "bin", binaryName),
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
for (const binaryPath of candidates) {
|
|
99
|
+
if (fs.existsSync(binaryPath)) {
|
|
100
|
+
return { binaryPath, binaryName }
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
throw new Error(`Could not find platform binary (tried: ${candidatePackageNames().join(", ")})`)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// The bun-compiled binary must be executable for the wrapper's spawn() to run it
|
|
109
|
+
// without the user ever needing sudo. npm/tar can drop the +x bit on some
|
|
110
|
+
// installs, so re-assert it here. Best-effort: never throw.
|
|
111
|
+
function ensureExecutable(binaryPath) {
|
|
112
|
+
try {
|
|
113
|
+
fs.chmodSync(binaryPath, 0o755)
|
|
114
|
+
} catch (err) {
|
|
115
|
+
console.warn(`Could not set executable bit on ${binaryPath}: ${err.message}`)
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// macOS kills unsigned/foreign-signed binaries with SIGKILL. Ad-hoc re-sign the
|
|
120
|
+
// binary in place so it runs without Gatekeeper intervention (and without sudo).
|
|
121
|
+
// Best-effort: codesign may be absent or the file read-only — never fail install.
|
|
122
|
+
function codesignDarwin(binaryPath) {
|
|
123
|
+
if (os.platform() !== "darwin") return
|
|
124
|
+
try {
|
|
125
|
+
execFileSync("codesign", ["--remove-signature", binaryPath], { stdio: "ignore" })
|
|
126
|
+
} catch {
|
|
127
|
+
/* nothing to remove — fine */
|
|
128
|
+
}
|
|
129
|
+
try {
|
|
130
|
+
execFileSync(
|
|
131
|
+
"codesign",
|
|
132
|
+
["--sign", "-", "--force", "--preserve-metadata=entitlements,requirements,flags,runtime", binaryPath],
|
|
133
|
+
{ stdio: "ignore" },
|
|
134
|
+
)
|
|
135
|
+
} catch (err) {
|
|
136
|
+
console.warn(`Could not ad-hoc sign ${binaryPath}: ${err.message}`)
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function prepareBinDirectory(binaryName) {
|
|
141
|
+
const binDir = path.join(__dirname, "bin")
|
|
142
|
+
const targetPath = path.join(binDir, binaryName)
|
|
143
|
+
|
|
144
|
+
// Ensure bin directory exists
|
|
145
|
+
if (!fs.existsSync(binDir)) {
|
|
146
|
+
fs.mkdirSync(binDir, { recursive: true })
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Remove existing binary/symlink if it exists
|
|
150
|
+
if (fs.existsSync(targetPath) || fs.lstatSync(targetPath, { throwIfNoEntry: false })) {
|
|
151
|
+
try {
|
|
152
|
+
fs.unlinkSync(targetPath)
|
|
153
|
+
} catch {
|
|
154
|
+
/* ignore — overwritten below */
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return { binDir, targetPath }
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Make the platform binary reachable at <pkg>/bin/<name>. Prefer a symlink; if
|
|
162
|
+
// the filesystem/permissions reject symlinks (Windows without dev mode, locked
|
|
163
|
+
// dirs), copy instead. The runtime wrapper resolves the binary on its own, so a
|
|
164
|
+
// failure here is non-fatal and must never break the npm install.
|
|
165
|
+
function linkBinary(sourcePath, binaryName) {
|
|
166
|
+
let targetPath
|
|
167
|
+
try {
|
|
168
|
+
;({ targetPath } = prepareBinDirectory(binaryName))
|
|
169
|
+
} catch (err) {
|
|
170
|
+
console.warn(`Could not prepare bin directory: ${err.message}`)
|
|
171
|
+
return
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
try {
|
|
175
|
+
fs.symlinkSync(sourcePath, targetPath)
|
|
176
|
+
console.log(`100xprompt binary symlinked: ${targetPath} -> ${sourcePath}`)
|
|
177
|
+
} catch (symlinkErr) {
|
|
178
|
+
try {
|
|
179
|
+
fs.copyFileSync(sourcePath, targetPath)
|
|
180
|
+
ensureExecutable(targetPath)
|
|
181
|
+
console.log(`100xprompt binary copied: ${targetPath}`)
|
|
182
|
+
} catch (copyErr) {
|
|
183
|
+
// Wrapper still resolves the binary directly; just warn.
|
|
184
|
+
console.warn(`Could not link binary (symlink: ${symlinkErr.message}; copy: ${copyErr.message})`)
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function createConfig() {
|
|
190
|
+
const home = os.homedir()
|
|
191
|
+
// Primary: XDG config path (what the app reads via xdg-basedir)
|
|
192
|
+
const xdgConfigHome = process.env.XDG_CONFIG_HOME || path.join(home, ".config")
|
|
193
|
+
const configDir = path.join(xdgConfigHome, "100xprompt")
|
|
194
|
+
const configPath = path.join(configDir, "100xprompt.json")
|
|
195
|
+
// Legacy fallback: also check ~/.100xprompt
|
|
196
|
+
const legacyConfigDir = path.join(home, ".100xprompt")
|
|
197
|
+
const legacyConfigPath = path.join(legacyConfigDir, "100xprompt.json")
|
|
198
|
+
|
|
199
|
+
const defaultConfig = {
|
|
200
|
+
$schema: "https://proxy.100xprompt.com/config.json",
|
|
201
|
+
compaction: {
|
|
202
|
+
auto: {
|
|
203
|
+
enabled: true,
|
|
204
|
+
// Base auto-compaction threshold. The code uses model-aware thresholds:
|
|
205
|
+
// Pro ~160k, Flash ~500k; this value is the fallback for other models.
|
|
206
|
+
compactionThreshold: 160000,
|
|
207
|
+
tailPreserveTurns: 5,
|
|
208
|
+
},
|
|
209
|
+
micro: {
|
|
210
|
+
enabled: true,
|
|
211
|
+
hotTailCount: 5,
|
|
212
|
+
},
|
|
213
|
+
},
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
try {
|
|
217
|
+
// Determine which config path to use:
|
|
218
|
+
// If legacy path exists but XDG path doesn't, use legacy for validation/merge
|
|
219
|
+
// Otherwise prefer XDG path (the app reads from both)
|
|
220
|
+
let activeConfigDir = configDir
|
|
221
|
+
let activeConfigPath = configPath
|
|
222
|
+
if (fs.existsSync(legacyConfigPath) && !fs.existsSync(configPath)) {
|
|
223
|
+
activeConfigDir = legacyConfigDir
|
|
224
|
+
activeConfigPath = legacyConfigPath
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (!fs.existsSync(activeConfigDir)) {
|
|
228
|
+
fs.mkdirSync(activeConfigDir, { recursive: true })
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
if (fs.existsSync(activeConfigPath)) {
|
|
232
|
+
// Validate existing config and merge missing defaults
|
|
233
|
+
try {
|
|
234
|
+
const raw = fs.readFileSync(activeConfigPath, "utf-8")
|
|
235
|
+
const existing = JSON.parse(raw)
|
|
236
|
+
|
|
237
|
+
let needsUpdate = false
|
|
238
|
+
|
|
239
|
+
// Ensure compaction section exists with all required fields
|
|
240
|
+
if (!existing.compaction) {
|
|
241
|
+
existing.compaction = defaultConfig.compaction
|
|
242
|
+
needsUpdate = true
|
|
243
|
+
} else {
|
|
244
|
+
if (!existing.compaction.auto) {
|
|
245
|
+
existing.compaction.auto = defaultConfig.compaction.auto
|
|
246
|
+
needsUpdate = true
|
|
247
|
+
} else {
|
|
248
|
+
// Merge individual auto fields if missing
|
|
249
|
+
for (const [key, value] of Object.entries(defaultConfig.compaction.auto)) {
|
|
250
|
+
if (existing.compaction.auto[key] === undefined) {
|
|
251
|
+
existing.compaction.auto[key] = value
|
|
252
|
+
needsUpdate = true
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
if (!existing.compaction.micro) {
|
|
257
|
+
existing.compaction.micro = defaultConfig.compaction.micro
|
|
258
|
+
needsUpdate = true
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
if (!existing.$schema) {
|
|
263
|
+
existing.$schema = defaultConfig.$schema
|
|
264
|
+
needsUpdate = true
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if (needsUpdate) {
|
|
268
|
+
fs.writeFileSync(activeConfigPath, JSON.stringify(existing, null, 2))
|
|
269
|
+
try {
|
|
270
|
+
fs.chmodSync(activeConfigPath, 0o644)
|
|
271
|
+
} catch {}
|
|
272
|
+
console.log(`Config updated with missing defaults at ${activeConfigPath}`)
|
|
273
|
+
} else {
|
|
274
|
+
console.log(`Config already exists and is valid at ${activeConfigPath}, skipping.`)
|
|
275
|
+
}
|
|
276
|
+
} catch (parseError) {
|
|
277
|
+
// Existing file is corrupt — back it up and recreate
|
|
278
|
+
const backupPath = activeConfigPath + ".bak"
|
|
279
|
+
console.warn(`Config at ${activeConfigPath} is corrupt, backing up to ${backupPath} and recreating.`)
|
|
280
|
+
try {
|
|
281
|
+
fs.copyFileSync(activeConfigPath, backupPath)
|
|
282
|
+
} catch {}
|
|
283
|
+
fs.writeFileSync(activeConfigPath, JSON.stringify(defaultConfig, null, 2))
|
|
284
|
+
try {
|
|
285
|
+
fs.chmodSync(activeConfigPath, 0o644)
|
|
286
|
+
} catch {}
|
|
287
|
+
console.log(`Default config recreated at ${activeConfigPath}`)
|
|
288
|
+
}
|
|
289
|
+
return
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
fs.writeFileSync(activeConfigPath, JSON.stringify(defaultConfig, null, 2))
|
|
293
|
+
try {
|
|
294
|
+
fs.chmodSync(activeConfigPath, 0o644)
|
|
295
|
+
} catch {}
|
|
296
|
+
console.log(`Default config created at ${activeConfigPath}`)
|
|
297
|
+
} catch (error) {
|
|
298
|
+
console.warn(`Failed to create default config: ${error.message}`)
|
|
299
|
+
// Don't fail postinstall just because config creation failed
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function buildBanner(color) {
|
|
304
|
+
const p = (code, s) => (color ? `\x1b[${code}m${s}\x1b[0m` : s)
|
|
305
|
+
const bold = (s) => p("1", s)
|
|
306
|
+
const dim = (s) => p("2", s)
|
|
307
|
+
const accent = (s) => p("38;5;215", s) // soft orange
|
|
308
|
+
const violet = (s) => p("38;5;141", s)
|
|
309
|
+
const cyan = (s) => p("38;5;117", s)
|
|
310
|
+
const green = (s) => p("38;5;114", s)
|
|
311
|
+
const pink = (s) => p("38;5;213", s)
|
|
312
|
+
|
|
313
|
+
const rule = violet(" " + "━".repeat(48))
|
|
314
|
+
const cmd = color ? `\x1b[1m\x1b[30m\x1b[48;5;215m 100xprompt \x1b[0m` : "100xprompt"
|
|
315
|
+
|
|
316
|
+
return [
|
|
317
|
+
"",
|
|
318
|
+
rule,
|
|
319
|
+
"",
|
|
320
|
+
` ${accent("✦")} ${bold("100XPrompt Pro")} ${dim("· AI coding assistant in your terminal")}`,
|
|
321
|
+
"",
|
|
322
|
+
` ${green("▸")} Run ${cmd} ${dim("to launch")}`,
|
|
323
|
+
` ${dim(" then type your first prompt and hit enter")}`,
|
|
324
|
+
"",
|
|
325
|
+
` ${dim("made with")} ${pink("♥")} ${dim("by the")} ${cyan("100xprompt")} ${dim("team")}`,
|
|
326
|
+
"",
|
|
327
|
+
rule,
|
|
328
|
+
"",
|
|
329
|
+
"",
|
|
330
|
+
].join("\n")
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
function printBanner() {
|
|
334
|
+
// npm hides postinstall stdout by default, so write straight to the controlling
|
|
335
|
+
// terminal (/dev/tty) — that bypasses npm's capture and shows the banner on install.
|
|
336
|
+
try {
|
|
337
|
+
fs.writeFileSync("/dev/tty", buildBanner(true))
|
|
338
|
+
} catch {
|
|
339
|
+
process.stdout.write(buildBanner(!!process.stdout.isTTY))
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
async function main() {
|
|
344
|
+
// Binary setup is best-effort: the runtime wrapper (bin/100xprompt.js) resolves
|
|
345
|
+
// and launches the platform binary on its own, so postinstall must NEVER fail
|
|
346
|
+
// the npm install — a non-zero exit is exactly what pushes users to retry with
|
|
347
|
+
// sudo. We only normalize permissions/signing so the binary runs unprivileged.
|
|
348
|
+
try {
|
|
349
|
+
const { binaryPath, binaryName } = findBinary()
|
|
350
|
+
ensureExecutable(binaryPath) // fixes "permission denied" → no sudo needed to run
|
|
351
|
+
codesignDarwin(binaryPath) // prevents macOS SIGKILL of the unsigned binary
|
|
352
|
+
linkBinary(binaryPath, binaryName)
|
|
353
|
+
} catch (error) {
|
|
354
|
+
// Don't fail — the wrapper still finds the binary at runtime.
|
|
355
|
+
console.warn(`100xprompt binary setup skipped: ${error.message}`)
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
try {
|
|
359
|
+
createConfig()
|
|
360
|
+
} catch (error) {
|
|
361
|
+
console.warn(`Config setup skipped: ${error.message}`)
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
printBanner()
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
main().catch((error) => {
|
|
368
|
+
// Always exit 0: a failed postinstall blocks the whole install and is the
|
|
369
|
+
// reason users reach for sudo. Never let that happen.
|
|
370
|
+
console.warn("Postinstall completed with warnings:", error?.message ?? error)
|
|
371
|
+
process.exit(0)
|
|
372
|
+
})
|