pandacode 0.0.0-stage → 0.3.18

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 ADDED
@@ -0,0 +1,29 @@
1
+ PandaCode License (MIT License with an attribution requirement)
2
+
3
+ Copyright (c) 2026 PandaCode contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ If a modified version of the Software, or any work based on it (a "fork"), is
16
+ used commercially or released publicly in any form (including as source code,
17
+ a binary, an app, a hosted service or a website), the fork must state "This is
18
+ a fork of PandaCode" together with a link to
19
+ https://github.com/aicodeking/pandacode. The statement must appear in the
20
+ fork's README or main documentation, and in a place its users can readily see,
21
+ such as its About screen, website or store listing.
22
+
23
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29
+ SOFTWARE.
@@ -0,0 +1,21 @@
1
+ #!/usr/bin/env node
2
+ import { readFileSync } from 'node:fs';
3
+ import { install } from '../installer.mjs';
4
+
5
+ const args = process.argv.slice(2);
6
+ if (args.includes('--help') || args.includes('-h')) {
7
+ console.log(`Usage: npx pandacode [--dry-run] [--help] [--version]
8
+
9
+ Downloads the latest PandaCode release from GitHub, verifies its signature and
10
+ installs it (macOS Apple silicon, Windows x64, Linux x64).
11
+
12
+ --dry-run download and verify only
13
+ --version print this installer's version`);
14
+ } else if (args.includes('--version') || args.includes('-v')) {
15
+ console.log(JSON.parse(readFileSync(new URL('../package.json', import.meta.url))).version);
16
+ } else {
17
+ install({ dryRun: args.includes('--dry-run') }).catch(error => {
18
+ console.error(`pandacode: ${error.message}`);
19
+ process.exit(1);
20
+ });
21
+ }
package/icon.png ADDED
Binary file
package/installer.mjs ADDED
@@ -0,0 +1,146 @@
1
+ // Downloads the latest PandaCode release, verifies its Tauri updater (minisign)
2
+ // signature with node:crypto, and installs it. Zero dependencies.
3
+ import { createHash, createPublicKey, verify } from 'node:crypto';
4
+ import { spawnSync, spawn } from 'node:child_process';
5
+ import { accessSync, constants, copyFileSync, chmodSync, existsSync, mkdirSync, mkdtempSync, readdirSync, renameSync, rmSync, writeFileSync } from 'node:fs';
6
+ import { homedir, tmpdir } from 'node:os';
7
+ import { dirname, join } from 'node:path';
8
+ import { fileURLToPath } from 'node:url';
9
+
10
+ // plugins.updater.pubkey from src-tauri/tauri.conf.json (a test keeps them equal).
11
+ export const PUBKEY = 'dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDM3OEQ0RkYwQ0I4QzRGODEKUldTQlQ0ekw4RStOTnhNYW9jL1pTZENocVU3TGNPeVVxcDdzdkN4d0s3R2lPekxGRitGLzVwM3oK';
12
+ export const BASE = 'https://github.com/aicodeking/pandacode/releases/latest/download';
13
+ const ASSETS = {
14
+ 'darwin-arm64': 'PandaCode-macOS-arm64.app.tar.gz',
15
+ 'win32-x64': 'PandaCode-Windows-x64-setup.exe',
16
+ 'linux-x64': 'PandaCode-Linux-x86_64.AppImage',
17
+ };
18
+
19
+ export function assetFor(platform = process.platform, arch = process.arch) {
20
+ const asset = ASSETS[`${platform}-${arch}`];
21
+ if (!asset) throw new Error(`PandaCode has no build for ${platform}-${arch}. Supported: macOS Apple silicon, Windows x64, Linux x64.`);
22
+ return asset;
23
+ }
24
+
25
+ const b64 = text => Buffer.from(text.trim(), 'base64');
26
+ const lines = buffer => buffer.toString('utf8').split('\n');
27
+ const SPKI_PREFIX = Buffer.from('302a300506032b6570032100', 'hex');
28
+ const key = pk => createPublicKey({ key: Buffer.concat([SPKI_PREFIX, pk]), format: 'der', type: 'spki' });
29
+
30
+ /** Verify `data` against a Tauri `.sig` file (base64 of minisign text). Throws on any mismatch. */
31
+ export function verifyMinisign(data, sigFile, pubkey = PUBKEY) {
32
+ const pub = b64(lines(b64(pubkey))[1]);
33
+ const sigLines = lines(b64(sigFile));
34
+ const sig = b64(sigLines[1]);
35
+ if (pub.length !== 42 || pub.subarray(0, 2).toString() !== 'Ed') throw new Error('Bad public key.');
36
+ if (sig.length !== 74) throw new Error('Bad signature.');
37
+ const alg = sig.subarray(0, 2).toString();
38
+ if (alg !== 'ED' && alg !== 'Ed') throw new Error('Unknown signature algorithm.');
39
+ if (!pub.subarray(2, 10).equals(sig.subarray(2, 10))) throw new Error('Signature was made with a different key.');
40
+ const pk = key(pub.subarray(10));
41
+ const message = alg === 'ED' ? createHash('blake2b512').update(data).digest() : data;
42
+ const raw = sig.subarray(10);
43
+ if (!verify(null, message, pk, raw)) throw new Error('Signature does not match the download.');
44
+ const prefix = 'trusted comment: ';
45
+ if (!sigLines[2]?.startsWith(prefix)) throw new Error('Signature has no trusted comment.');
46
+ const comment = Buffer.from(sigLines[2].slice(prefix.length));
47
+ if (!verify(null, Buffer.concat([raw, comment]), pk, b64(sigLines[3] ?? ''))) throw new Error('Signature comment does not match.');
48
+ }
49
+
50
+ export function desktopEntry(exec, icon) {
51
+ return `[Desktop Entry]\nType=Application\nName=PandaCode\nComment=Desktop app for coding agents\nExec=${exec}\nIcon=${icon}\nTerminal=false\nCategories=Development;\nStartupWMClass=PandaCode\n`;
52
+ }
53
+
54
+ async function download(url, file) {
55
+ const response = await fetch(url, { redirect: 'follow' });
56
+ if (!response.ok) throw new Error(`Download failed (${response.status}) for ${url}`);
57
+ const total = Number(response.headers.get('content-length')) || 0;
58
+ const chunks = [];
59
+ let got = 0;
60
+ let shown = 0;
61
+ for await (const chunk of response.body) {
62
+ chunks.push(chunk);
63
+ got += chunk.length;
64
+ if (got - shown < 1e6 && got < total) continue;
65
+ shown = got;
66
+ const mb = (got / 1e6).toFixed(1);
67
+ process.stderr.write(`\rDownloading ${file}: ${mb} MB${total ? ` (${Math.floor(got / total * 100)}%)` : ''}`);
68
+ }
69
+ process.stderr.write('\n');
70
+ return Buffer.concat(chunks);
71
+ }
72
+
73
+ function run(command, args, options = {}) {
74
+ const result = spawnSync(command, args, { encoding: 'utf8', ...options });
75
+ if (result.error) throw result.error;
76
+ return result;
77
+ }
78
+
79
+ function installMac(archive, dir) {
80
+ const extracted = join(dir, 'extracted');
81
+ mkdirSync(extracted);
82
+ const tar = run('tar', ['-xzf', archive, '-C', extracted]);
83
+ if (tar.status !== 0) throw new Error(`tar failed: ${tar.stderr}`);
84
+ const app = join(extracted, 'PandaCode.app');
85
+ if (!existsSync(app)) throw new Error('The archive does not contain PandaCode.app.');
86
+ let root = '/Applications';
87
+ try { accessSync(root, constants.W_OK); } catch { root = join(homedir(), 'Applications'); mkdirSync(root, { recursive: true }); }
88
+ const target = join(root, 'PandaCode.app');
89
+ const old = join(root, `.PandaCode-old-${process.pid}.app`);
90
+ if (existsSync(target)) renameSync(target, old);
91
+ try { renameSync(app, target); } catch { // different volume
92
+ const copy = run('cp', ['-R', app, target]);
93
+ if (copy.status !== 0) { if (existsSync(old)) renameSync(old, target); throw new Error(`Could not copy the app: ${copy.stderr}`); }
94
+ }
95
+ rmSync(old, { recursive: true, force: true });
96
+ const macos = join(target, 'Contents', 'MacOS');
97
+ const names = readdirSync(macos);
98
+ const binary = names.find(n => n === 'pandacode') ?? names[0];
99
+ const sign = run(join(macos, binary), ['--local-sign']);
100
+ if (sign.status !== 0) throw new Error(`Local signing failed:\n${sign.stdout}${sign.stderr}`);
101
+ run('open', [target]);
102
+ return target;
103
+ }
104
+
105
+ function installWindows(setup) {
106
+ const result = run(setup, ['/S'], { stdio: 'inherit' });
107
+ if (result.status !== 0) throw new Error(`The installer exited with ${result.status}.`);
108
+ const base = process.env.LOCALAPPDATA ?? join(homedir(), 'AppData', 'Local');
109
+ const exe = ['PandaCode', 'Programs\\PandaCode'].map(d => join(base, d, 'PandaCode.exe')).find(existsSync);
110
+ if (!exe) return 'PandaCode (installed; start it from the Start menu)';
111
+ spawn(exe, [], { detached: true, stdio: 'ignore' }).unref();
112
+ return exe;
113
+ }
114
+
115
+ function installLinux(appImage) {
116
+ const bin = join(homedir(), '.local', 'bin');
117
+ const share = join(homedir(), '.local', 'share');
118
+ mkdirSync(bin, { recursive: true });
119
+ mkdirSync(join(share, 'applications'), { recursive: true });
120
+ mkdirSync(join(share, 'icons'), { recursive: true });
121
+ const target = join(bin, 'pandacode');
122
+ copyFileSync(appImage, target);
123
+ chmodSync(target, 0o755);
124
+ const icon = join(share, 'icons', 'pandacode.png');
125
+ copyFileSync(join(dirname(fileURLToPath(import.meta.url)), 'icon.png'), icon);
126
+ writeFileSync(join(share, 'applications', 'pandacode.desktop'), desktopEntry(target, icon));
127
+ spawn(target, [], { detached: true, stdio: 'ignore' }).unref();
128
+ return target;
129
+ }
130
+
131
+ export async function install({ dryRun = false } = {}) {
132
+ const asset = assetFor();
133
+ const dir = mkdtempSync(join(tmpdir(), 'pandacode-'));
134
+ try {
135
+ const file = join(dir, asset);
136
+ const [data, sig] = [await download(`${BASE}/${asset}`, asset), await download(`${BASE}/${asset}.sig`, `${asset}.sig`)];
137
+ verifyMinisign(data, sig.toString('utf8'));
138
+ console.log('Signature verified.');
139
+ if (dryRun) return console.log('Dry run: nothing installed.');
140
+ writeFileSync(file, data);
141
+ const where = process.platform === 'darwin' ? installMac(file, dir) : process.platform === 'win32' ? installWindows(file) : installLinux(file);
142
+ console.log(`Installed PandaCode at ${where}`);
143
+ } finally {
144
+ rmSync(dir, { recursive: true, force: true });
145
+ }
146
+ }
package/package.json CHANGED
@@ -1,6 +1,25 @@
1
1
  {
2
2
  "name": "pandacode",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.3.18",
4
+ "description": "Install PandaCode, the open-source desktop app for coding agents, with npx pandacode.",
5
+ "license": "SEE LICENSE IN LICENSE",
6
+ "type": "module",
7
+ "bin": {
8
+ "pandacode": "bin/pandacode.mjs"
9
+ },
10
+ "files": [
11
+ "bin",
12
+ "installer.mjs",
13
+ "icon.png",
14
+ "LICENSE"
15
+ ],
16
+ "engines": {
17
+ "node": ">=18"
18
+ },
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "git+https://github.com/aicodeking/pandacode.git",
22
+ "directory": "packages/npx"
23
+ },
24
+ "homepage": "https://github.com/aicodeking/pandacode"
25
+ }
package/README.md DELETED
@@ -1,3 +0,0 @@
1
- # Temporary Holding Version
2
-
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.