concentus 0.1.4

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.
Files changed (2) hide show
  1. package/bin/concentus.mjs +118 -0
  2. package/package.json +34 -0
@@ -0,0 +1,118 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * `npx concentus` — the desktop app, reached from the tool people already have open.
4
+ *
5
+ * The package deliberately contains no application: 300 MB inside an npm tarball would be a copy
6
+ * the registry keeps forever, per version, and npm is the wrong CDN for it. Instead this fetches
7
+ * the right installer for the platform from the same GitHub release every other channel uses,
8
+ * verifies nothing less than HTTPS + the published size, and runs it. One artifact per release,
9
+ * however many doors lead to it.
10
+ *
11
+ * Honest limits, stated rather than discovered: macOS has no build yet (say so and exit), and on
12
+ * Linux the AppImage is downloaded and made executable but sandbox quirks belong to the distro.
13
+ */
14
+ import { createWriteStream, existsSync, mkdirSync, chmodSync, statSync } from 'node:fs'
15
+ import { get } from 'node:https'
16
+ import { homedir, platform, tmpdir } from 'node:os'
17
+ import { join } from 'node:path'
18
+ import { spawn } from 'node:child_process'
19
+
20
+ const REPO = 'Gergilcan/concentus'
21
+
22
+ function fail(message) {
23
+ console.error('\n' + message)
24
+ process.exit(1)
25
+ }
26
+
27
+ /** GitHub API GET with redirects, small and dependency-free. */
28
+ function getJson(url) {
29
+ return new Promise((resolve, reject) => {
30
+ const req = get(url, { headers: { 'user-agent': 'concentus-npm-launcher', accept: 'application/vnd.github+json' } }, (res) => {
31
+ if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
32
+ return resolve(getJson(res.headers.location))
33
+ }
34
+ let body = ''
35
+ res.on('data', (c) => (body += c))
36
+ res.on('end', () => {
37
+ if (res.statusCode !== 200) return reject(new Error('GitHub answered HTTP ' + res.statusCode))
38
+ try { resolve(JSON.parse(body)) } catch (e) { reject(e) }
39
+ })
40
+ })
41
+ req.on('error', reject)
42
+ })
43
+ }
44
+
45
+ function download(url, to, expectedSize) {
46
+ return new Promise((resolve, reject) => {
47
+ const req = get(url, { headers: { 'user-agent': 'concentus-npm-launcher' } }, (res) => {
48
+ if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
49
+ return resolve(download(res.headers.location, to, expectedSize))
50
+ }
51
+ if (res.statusCode !== 200) return reject(new Error('download answered HTTP ' + res.statusCode))
52
+ const out = createWriteStream(to)
53
+ let got = 0
54
+ let lastPct = -10
55
+ res.on('data', (c) => {
56
+ got += c.length
57
+ const pct = Math.floor((got / expectedSize) * 100)
58
+ if (pct >= lastPct + 10) {
59
+ lastPct = pct
60
+ process.stdout.write('\rDownloading… ' + pct + '% ')
61
+ }
62
+ })
63
+ res.pipe(out)
64
+ out.on('finish', () => {
65
+ process.stdout.write('\rDownloading… done \n')
66
+ // The size is the published one or the file is not the published file. Not a signature,
67
+ // and not claimed to be one — but it catches a truncated or substituted download.
68
+ const actual = statSync(to).size
69
+ if (expectedSize && actual !== expectedSize) {
70
+ return reject(new Error('size mismatch: expected ' + expectedSize + ', got ' + actual))
71
+ }
72
+ resolve()
73
+ })
74
+ out.on('error', reject)
75
+ })
76
+ req.on('error', reject)
77
+ })
78
+ }
79
+
80
+ const os = platform()
81
+ if (os === 'darwin') {
82
+ fail(
83
+ 'There is no macOS build yet — it needs Apple notarization, which is on the roadmap.\n' +
84
+ 'Windows and Linux installers: https://github.com/' + REPO + '/releases/latest',
85
+ )
86
+ }
87
+ if (os !== 'win32' && os !== 'linux') {
88
+ fail('Unsupported platform: ' + os)
89
+ }
90
+
91
+ console.log('Concentus — fetching the latest release…')
92
+ const release = await getJson('https://api.github.com/repos/' + REPO + '/releases/latest').catch((e) =>
93
+ fail('Could not reach GitHub Releases: ' + e.message),
94
+ )
95
+
96
+ const wanted = os === 'win32' ? /^Concentus-Setup-.*\.exe$/ : /^Concentus-.*\.AppImage$/
97
+ const asset = release.assets.find((a) => wanted.test(a.name))
98
+ if (!asset) fail('The latest release (' + release.tag_name + ') has no installer for this platform.')
99
+
100
+ const cacheDir = join(os === 'win32' ? join(homedir(), 'AppData', 'Local') : join(homedir(), '.cache'), 'concentus-launcher')
101
+ mkdirSync(cacheDir, { recursive: true })
102
+ const target = join(cacheDir, asset.name)
103
+
104
+ if (existsSync(target) && statSync(target).size === asset.size) {
105
+ console.log('Already downloaded (' + asset.name + ').')
106
+ } else {
107
+ console.log(asset.name + ' (' + Math.round(asset.size / 1024 / 1024) + ' MB) from ' + release.tag_name)
108
+ await download(asset.browser_download_url, target, asset.size).catch((e) => fail('Download failed: ' + e.message))
109
+ }
110
+
111
+ if (os === 'win32') {
112
+ console.log('Starting the installer — it asks where to install and takes it from there.')
113
+ spawn(target, [], { detached: true, stdio: 'ignore' }).unref()
114
+ } else {
115
+ chmodSync(target, 0o755)
116
+ console.log('Starting ' + asset.name + ' — it runs in place; move it wherever you keep AppImages.')
117
+ spawn(target, [], { detached: true, stdio: 'ignore' }).unref()
118
+ }
package/package.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "concentus",
3
+ "version": "0.1.4",
4
+ "description": "Visual multi-agent AI orchestration that runs on your Claude subscription. This package downloads the desktop app's installer for your platform from GitHub Releases and launches it — the app itself is not inside the package.",
5
+ "bin": {
6
+ "concentus": "bin/concentus.mjs"
7
+ },
8
+ "files": [
9
+ "bin"
10
+ ],
11
+ "engines": {
12
+ "node": ">=18"
13
+ },
14
+ "os": [
15
+ "win32",
16
+ "linux"
17
+ ],
18
+ "keywords": [
19
+ "ai",
20
+ "agents",
21
+ "claude",
22
+ "orchestration",
23
+ "automation",
24
+ "workflow",
25
+ "mcp"
26
+ ],
27
+ "author": "Gerard Gilabert",
28
+ "license": "PolyForm-Noncommercial-1.0.0",
29
+ "repository": {
30
+ "type": "git",
31
+ "url": "git+https://github.com/Gergilcan/concentus.git"
32
+ },
33
+ "homepage": "https://github.com/Gergilcan/concentus"
34
+ }