100xprompt-cli 0.1.5

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.
@@ -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,23 @@
1
+ {
2
+ "name": "100xprompt-cli",
3
+ "version": "0.1.5",
4
+ "bin": {
5
+ "100xprompt": "bin/100xprompt.js"
6
+ },
7
+ "scripts": {
8
+ "postinstall": "bun ./postinstall.mjs || node ./postinstall.mjs"
9
+ },
10
+ "optionalDependencies": {
11
+ "@100xprompt/cli-linux-arm64": "0.1.5",
12
+ "@100xprompt/cli-linux-x64": "0.1.5",
13
+ "@100xprompt/cli-linux-x64-baseline": "0.1.5",
14
+ "@100xprompt/cli-linux-arm64-musl": "0.1.5",
15
+ "@100xprompt/cli-linux-x64-musl": "0.1.5",
16
+ "@100xprompt/cli-linux-x64-baseline-musl": "0.1.5",
17
+ "@100xprompt/cli-darwin-arm64": "0.1.5",
18
+ "@100xprompt/cli-darwin-x64": "0.1.5",
19
+ "@100xprompt/cli-darwin-x64-baseline": "0.1.5",
20
+ "@100xprompt/cli-windows-x64": "0.1.5",
21
+ "@100xprompt/cli-windows-x64-baseline": "0.1.5"
22
+ }
23
+ }
@@ -0,0 +1,366 @@
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(
106
+ `Could not find platform binary (tried: ${candidatePackageNames().join(", ")})`,
107
+ )
108
+ }
109
+
110
+ // The bun-compiled binary must be executable for the wrapper's spawn() to run it
111
+ // without the user ever needing sudo. npm/tar can drop the +x bit on some
112
+ // installs, so re-assert it here. Best-effort: never throw.
113
+ function ensureExecutable(binaryPath) {
114
+ try {
115
+ fs.chmodSync(binaryPath, 0o755)
116
+ } catch (err) {
117
+ console.warn(`Could not set executable bit on ${binaryPath}: ${err.message}`)
118
+ }
119
+ }
120
+
121
+ // macOS kills unsigned/foreign-signed binaries with SIGKILL. Ad-hoc re-sign the
122
+ // binary in place so it runs without Gatekeeper intervention (and without sudo).
123
+ // Best-effort: codesign may be absent or the file read-only — never fail install.
124
+ function codesignDarwin(binaryPath) {
125
+ if (os.platform() !== "darwin") return
126
+ try {
127
+ execFileSync("codesign", ["--remove-signature", binaryPath], { stdio: "ignore" })
128
+ } catch {
129
+ /* nothing to remove — fine */
130
+ }
131
+ try {
132
+ execFileSync(
133
+ "codesign",
134
+ ["--sign", "-", "--force", "--preserve-metadata=entitlements,requirements,flags,runtime", binaryPath],
135
+ { stdio: "ignore" },
136
+ )
137
+ } catch (err) {
138
+ console.warn(`Could not ad-hoc sign ${binaryPath}: ${err.message}`)
139
+ }
140
+ }
141
+
142
+ function prepareBinDirectory(binaryName) {
143
+ const binDir = path.join(__dirname, "bin")
144
+ const targetPath = path.join(binDir, binaryName)
145
+
146
+ // Ensure bin directory exists
147
+ if (!fs.existsSync(binDir)) {
148
+ fs.mkdirSync(binDir, { recursive: true })
149
+ }
150
+
151
+ // Remove existing binary/symlink if it exists
152
+ if (fs.existsSync(targetPath) || fs.lstatSync(targetPath, { throwIfNoEntry: false })) {
153
+ try {
154
+ fs.unlinkSync(targetPath)
155
+ } catch {
156
+ /* ignore — overwritten below */
157
+ }
158
+ }
159
+
160
+ return { binDir, targetPath }
161
+ }
162
+
163
+ // Make the platform binary reachable at <pkg>/bin/<name>. Prefer a symlink; if
164
+ // the filesystem/permissions reject symlinks (Windows without dev mode, locked
165
+ // dirs), copy instead. The runtime wrapper resolves the binary on its own, so a
166
+ // failure here is non-fatal and must never break the npm install.
167
+ function linkBinary(sourcePath, binaryName) {
168
+ let targetPath
169
+ try {
170
+ ;({ targetPath } = prepareBinDirectory(binaryName))
171
+ } catch (err) {
172
+ console.warn(`Could not prepare bin directory: ${err.message}`)
173
+ return
174
+ }
175
+
176
+ try {
177
+ fs.symlinkSync(sourcePath, targetPath)
178
+ console.log(`100xprompt binary symlinked: ${targetPath} -> ${sourcePath}`)
179
+ } catch (symlinkErr) {
180
+ try {
181
+ fs.copyFileSync(sourcePath, targetPath)
182
+ ensureExecutable(targetPath)
183
+ console.log(`100xprompt binary copied: ${targetPath}`)
184
+ } catch (copyErr) {
185
+ // Wrapper still resolves the binary directly; just warn.
186
+ console.warn(`Could not link binary (symlink: ${symlinkErr.message}; copy: ${copyErr.message})`)
187
+ }
188
+ }
189
+ }
190
+
191
+ function createConfig() {
192
+ const home = os.homedir()
193
+ // Primary: XDG config path (what the app reads via xdg-basedir)
194
+ const xdgConfigHome = process.env.XDG_CONFIG_HOME || path.join(home, ".config")
195
+ const configDir = path.join(xdgConfigHome, "100xprompt")
196
+ const configPath = path.join(configDir, "100xprompt.json")
197
+ // Legacy fallback: also check ~/.100xprompt
198
+ const legacyConfigDir = path.join(home, ".100xprompt")
199
+ const legacyConfigPath = path.join(legacyConfigDir, "100xprompt.json")
200
+
201
+ const defaultConfig = {
202
+ $schema: "https://proxy.100xprompt.com/config.json",
203
+ compaction: {
204
+ auto: {
205
+ enabled: true,
206
+ // Base auto-compaction threshold. The code uses model-aware thresholds:
207
+ // Pro ~160k, Flash ~500k; this value is the fallback for other models.
208
+ compactionThreshold: 160000,
209
+ tailPreserveTurns: 5,
210
+ },
211
+ micro: {
212
+ enabled: true,
213
+ hotTailCount: 5,
214
+ },
215
+ },
216
+ }
217
+
218
+ try {
219
+ // Determine which config path to use:
220
+ // If legacy path exists but XDG path doesn't, use legacy for validation/merge
221
+ // Otherwise prefer XDG path (the app reads from both)
222
+ let activeConfigDir = configDir
223
+ let activeConfigPath = configPath
224
+ if (fs.existsSync(legacyConfigPath) && !fs.existsSync(configPath)) {
225
+ activeConfigDir = legacyConfigDir
226
+ activeConfigPath = legacyConfigPath
227
+ }
228
+
229
+ if (!fs.existsSync(activeConfigDir)) {
230
+ fs.mkdirSync(activeConfigDir, { recursive: true })
231
+ }
232
+
233
+ if (fs.existsSync(activeConfigPath)) {
234
+ // Validate existing config and merge missing defaults
235
+ try {
236
+ const raw = fs.readFileSync(activeConfigPath, "utf-8")
237
+ const existing = JSON.parse(raw)
238
+
239
+ let needsUpdate = false
240
+
241
+ // Ensure compaction section exists with all required fields
242
+ if (!existing.compaction) {
243
+ existing.compaction = defaultConfig.compaction
244
+ needsUpdate = true
245
+ } else {
246
+ if (!existing.compaction.auto) {
247
+ existing.compaction.auto = defaultConfig.compaction.auto
248
+ needsUpdate = true
249
+ } else {
250
+ // Merge individual auto fields if missing
251
+ for (const [key, value] of Object.entries(defaultConfig.compaction.auto)) {
252
+ if (existing.compaction.auto[key] === undefined) {
253
+ existing.compaction.auto[key] = value
254
+ needsUpdate = true
255
+ }
256
+ }
257
+ }
258
+ if (!existing.compaction.micro) {
259
+ existing.compaction.micro = defaultConfig.compaction.micro
260
+ needsUpdate = true
261
+ }
262
+ }
263
+
264
+ if (!existing.$schema) {
265
+ existing.$schema = defaultConfig.$schema
266
+ needsUpdate = true
267
+ }
268
+
269
+ if (needsUpdate) {
270
+ fs.writeFileSync(activeConfigPath, JSON.stringify(existing, null, 2))
271
+ try { fs.chmodSync(activeConfigPath, 0o644) } 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 { fs.copyFileSync(activeConfigPath, backupPath) } catch { }
281
+ fs.writeFileSync(activeConfigPath, JSON.stringify(defaultConfig, null, 2))
282
+ try { fs.chmodSync(activeConfigPath, 0o644) } catch { }
283
+ console.log(`Default config recreated at ${activeConfigPath}`)
284
+ }
285
+ return
286
+ }
287
+
288
+ fs.writeFileSync(activeConfigPath, JSON.stringify(defaultConfig, null, 2))
289
+ try { fs.chmodSync(activeConfigPath, 0o644) } catch { }
290
+ console.log(`Default config created at ${activeConfigPath}`)
291
+ } catch (error) {
292
+ console.warn(`Failed to create default config: ${error.message}`)
293
+ // Don't fail postinstall just because config creation failed
294
+ }
295
+ }
296
+
297
+ function buildBanner(color) {
298
+ const p = (code, s) => (color ? `\x1b[${code}m${s}\x1b[0m` : s)
299
+ const bold = (s) => p("1", s)
300
+ const dim = (s) => p("2", s)
301
+ const accent = (s) => p("38;5;215", s) // soft orange
302
+ const violet = (s) => p("38;5;141", s)
303
+ const cyan = (s) => p("38;5;117", s)
304
+ const green = (s) => p("38;5;114", s)
305
+ const pink = (s) => p("38;5;213", s)
306
+
307
+ const rule = violet(" " + "━".repeat(48))
308
+ const cmd = color ? `\x1b[1m\x1b[30m\x1b[48;5;215m 100xprompt \x1b[0m` : "100xprompt"
309
+
310
+ return [
311
+ "",
312
+ rule,
313
+ "",
314
+ ` ${accent("✦")} ${bold("100XPrompt Pro")} ${dim("· AI coding assistant in your terminal")}`,
315
+ "",
316
+ ` ${green("▸")} Run ${cmd} ${dim("to launch")}`,
317
+ ` ${dim(" then type your first prompt and hit enter")}`,
318
+ "",
319
+ ` ${dim("made with")} ${pink("♥")} ${dim("by the")} ${cyan("100xprompt")} ${dim("team")}`,
320
+ "",
321
+ rule,
322
+ "",
323
+ "",
324
+ ].join("\n")
325
+ }
326
+
327
+ function printBanner() {
328
+ // npm hides postinstall stdout by default, so write straight to the controlling
329
+ // terminal (/dev/tty) — that bypasses npm's capture and shows the banner on install.
330
+ try {
331
+ fs.writeFileSync("/dev/tty", buildBanner(true))
332
+ } catch {
333
+ process.stdout.write(buildBanner(!!process.stdout.isTTY))
334
+ }
335
+ }
336
+
337
+ async function main() {
338
+ // Binary setup is best-effort: the runtime wrapper (bin/100xprompt.js) resolves
339
+ // and launches the platform binary on its own, so postinstall must NEVER fail
340
+ // the npm install — a non-zero exit is exactly what pushes users to retry with
341
+ // sudo. We only normalize permissions/signing so the binary runs unprivileged.
342
+ try {
343
+ const { binaryPath, binaryName } = findBinary()
344
+ ensureExecutable(binaryPath) // fixes "permission denied" → no sudo needed to run
345
+ codesignDarwin(binaryPath) // prevents macOS SIGKILL of the unsigned binary
346
+ linkBinary(binaryPath, binaryName)
347
+ } catch (error) {
348
+ // Don't fail — the wrapper still finds the binary at runtime.
349
+ console.warn(`100xprompt binary setup skipped: ${error.message}`)
350
+ }
351
+
352
+ try {
353
+ createConfig()
354
+ } catch (error) {
355
+ console.warn(`Config setup skipped: ${error.message}`)
356
+ }
357
+
358
+ printBanner()
359
+ }
360
+
361
+ main().catch((error) => {
362
+ // Always exit 0: a failed postinstall blocks the whole install and is the
363
+ // reason users reach for sudo. Never let that happen.
364
+ console.warn("Postinstall completed with warnings:", error?.message ?? error)
365
+ process.exit(0)
366
+ })