@twira/cli 2.0.1
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.md +25 -0
- package/README.md +48 -0
- package/bin/twira.js +20 -0
- package/install.js +189 -0
- package/package.json +40 -0
package/LICENSE.md
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Twira — Proprietary Software
|
|
2
|
+
|
|
3
|
+
Copyright © Twira Ltd. All rights reserved.
|
|
4
|
+
|
|
5
|
+
Twira is proprietary software, licensed (not sold) under the Twira
|
|
6
|
+
End User Licence Agreement (EULA). By downloading, installing, or using this
|
|
7
|
+
software you agree to the EULA.
|
|
8
|
+
|
|
9
|
+
**The full, binding EULA is maintained in one place — our website:**
|
|
10
|
+
https://twira.com/legal/eula
|
|
11
|
+
|
|
12
|
+
Related terms:
|
|
13
|
+
|
|
14
|
+
- Subscription Terms — https://twira.com/legal/terms
|
|
15
|
+
- Privacy Policy — https://twira.com/legal/privacy
|
|
16
|
+
- Data Processing Addendum — https://twira.com/legal/dpa
|
|
17
|
+
|
|
18
|
+
## Summary (the EULA at the link above is authoritative)
|
|
19
|
+
|
|
20
|
+
- **Personal Use** — free, for ever. No signup, no card.
|
|
21
|
+
- **Commercial Use** — 14-day free trial, then a Pro subscription.
|
|
22
|
+
- **Education** — full Pro capability, free for verified students and staff.
|
|
23
|
+
- **Enterprise** — bespoke; contact us.
|
|
24
|
+
- You may not reverse engineer, redistribute, or use it to build a competing product.
|
|
25
|
+
- The software is provided "as is", without warranty. See the EULA for full terms.
|
package/README.md
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="https://twira.com/brand/icon-512.png" width="96" alt="Twira">
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
<h1 align="center">Twira</h1>
|
|
6
|
+
|
|
7
|
+
<p align="center">
|
|
8
|
+
Power tools for AI agents — fewer hallucinations, fewer tokens, faster work.
|
|
9
|
+
</p>
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
Twira indexes your codebase locally and plugs into Claude Code, Codex, Gemini, and any MCP-compatible assistant. It gives your AI ground truth — fast, local, and deterministic. Your assistant gets sharper. You stay in flow.
|
|
14
|
+
|
|
15
|
+
Zero dependencies. Runs locally. Your source code never leaves your machine.
|
|
16
|
+
|
|
17
|
+
## Install
|
|
18
|
+
|
|
19
|
+
```sh
|
|
20
|
+
npm install -g @twira/cli
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
This package downloads the right native `twira` binary for your platform on install. Other channels:
|
|
24
|
+
|
|
25
|
+
```sh
|
|
26
|
+
# Homebrew (macOS / Linux)
|
|
27
|
+
brew install twirahq/tap/twira
|
|
28
|
+
|
|
29
|
+
# curl (macOS / Linux)
|
|
30
|
+
curl -fsSL https://raw.githubusercontent.com/TwiraHQ/twira/main/install.sh | sh
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Get started
|
|
34
|
+
|
|
35
|
+
```sh
|
|
36
|
+
twira init # set up Twira in your project
|
|
37
|
+
twira index # build the local code graph
|
|
38
|
+
twira mcp # run the MCP server your AI agent connects to
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Links
|
|
42
|
+
|
|
43
|
+
- Website — https://twira.com
|
|
44
|
+
- Documentation & releases — https://github.com/TwiraHQ/twira
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
The product is in alpha. The download works today.
|
package/bin/twira.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
|
|
4
|
+
const { execFileSync } = require("child_process");
|
|
5
|
+
const path = require("path");
|
|
6
|
+
|
|
7
|
+
const ext = process.platform === "win32" ? ".exe" : "";
|
|
8
|
+
const binary = path.join(__dirname, `twira${ext}`);
|
|
9
|
+
|
|
10
|
+
try {
|
|
11
|
+
execFileSync(binary, process.argv.slice(2), { stdio: "inherit" });
|
|
12
|
+
} catch (err) {
|
|
13
|
+
if (err.status !== null) {
|
|
14
|
+
process.exit(err.status);
|
|
15
|
+
}
|
|
16
|
+
console.error(`Failed to run Twira: ${err.message}`);
|
|
17
|
+
console.error(`Expected binary at: ${binary}`);
|
|
18
|
+
console.error(`Try reinstalling: npm install -g @twira/cli`);
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
package/install.js
ADDED
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
|
|
4
|
+
const { execSync } = require("child_process");
|
|
5
|
+
const fs = require("fs");
|
|
6
|
+
const https = require("https");
|
|
7
|
+
const path = require("path");
|
|
8
|
+
const os = require("os");
|
|
9
|
+
const { createHash } = require("crypto");
|
|
10
|
+
|
|
11
|
+
const REPO = "TwiraHQ/twira";
|
|
12
|
+
const BINARY_NAME = "twira";
|
|
13
|
+
|
|
14
|
+
const PLATFORM_MAP = {
|
|
15
|
+
"darwin-x64": "x86_64-apple-darwin",
|
|
16
|
+
"darwin-arm64": "aarch64-apple-darwin",
|
|
17
|
+
"linux-x64": "x86_64-unknown-linux-gnu",
|
|
18
|
+
"linux-arm64": "aarch64-unknown-linux-gnu",
|
|
19
|
+
"win32-x64": "x86_64-pc-windows-msvc",
|
|
20
|
+
"win32-arm64": "aarch64-pc-windows-msvc",
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const ARCHIVE_EXT = {
|
|
24
|
+
darwin: "tar.gz",
|
|
25
|
+
linux: "tar.gz",
|
|
26
|
+
win32: "zip",
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
function getPlatformTarget() {
|
|
30
|
+
const key = `${process.platform}-${process.arch}`;
|
|
31
|
+
const target = PLATFORM_MAP[key];
|
|
32
|
+
if (!target) {
|
|
33
|
+
console.error(`Unsupported platform: ${key}`);
|
|
34
|
+
console.error(`Supported: ${Object.keys(PLATFORM_MAP).join(", ")}`);
|
|
35
|
+
process.exit(1);
|
|
36
|
+
}
|
|
37
|
+
return target;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function getVersion() {
|
|
41
|
+
const pkg = require("./package.json");
|
|
42
|
+
// npm version may be "2.0.0-alpha.1" — tag is "v2.0.0-alpha.1"
|
|
43
|
+
return `v${pkg.version}`;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function fetch(url) {
|
|
47
|
+
return new Promise((resolve, reject) => {
|
|
48
|
+
const get = (url, redirectCount) => {
|
|
49
|
+
if (redirectCount > 5) return reject(new Error("Too many redirects"));
|
|
50
|
+
https
|
|
51
|
+
.get(url, { headers: { "User-Agent": "twira-npm-installer" } }, (res) => {
|
|
52
|
+
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
|
53
|
+
return get(res.headers.location, redirectCount + 1);
|
|
54
|
+
}
|
|
55
|
+
if (res.statusCode !== 200) {
|
|
56
|
+
return reject(new Error(`HTTP ${res.statusCode} for ${url}`));
|
|
57
|
+
}
|
|
58
|
+
const chunks = [];
|
|
59
|
+
res.on("data", (chunk) => chunks.push(chunk));
|
|
60
|
+
res.on("end", () => resolve(Buffer.concat(chunks)));
|
|
61
|
+
res.on("error", reject);
|
|
62
|
+
})
|
|
63
|
+
.on("error", reject);
|
|
64
|
+
};
|
|
65
|
+
get(url, 0);
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async function downloadAndVerify(version, target) {
|
|
70
|
+
const ext = ARCHIVE_EXT[process.platform];
|
|
71
|
+
const archiveName = `${BINARY_NAME}-${version}-${target}.${ext}`;
|
|
72
|
+
const baseUrl = `https://github.com/${REPO}/releases/download/${version}`;
|
|
73
|
+
|
|
74
|
+
console.log(`Downloading Twira ${version} for ${target}...`);
|
|
75
|
+
|
|
76
|
+
// ES256 signature sidecar — present from v2.0.0-alpha.8 onwards. Older
|
|
77
|
+
// releases had no .sig file or legacy Ed25519 sigs that were never
|
|
78
|
+
// actually verified; we don't fail if it's missing or 404s.
|
|
79
|
+
let sigFile = null;
|
|
80
|
+
const [archive, checksumFile, sigResp] = await Promise.all([
|
|
81
|
+
fetch(`${baseUrl}/${archiveName}`),
|
|
82
|
+
fetch(`${baseUrl}/${archiveName}.sha256`),
|
|
83
|
+
fetchOptional(`${baseUrl}/${archiveName}.sig`),
|
|
84
|
+
]);
|
|
85
|
+
if (sigResp) {
|
|
86
|
+
sigFile = sigResp;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Verify checksum (mandatory — fails install if mismatch)
|
|
90
|
+
const expectedHash = checksumFile.toString("utf8").trim().split(/\s+/)[0];
|
|
91
|
+
const actualHash = createHash("sha256").update(archive).digest("hex");
|
|
92
|
+
|
|
93
|
+
if (expectedHash !== actualHash) {
|
|
94
|
+
console.error(`Checksum mismatch!`);
|
|
95
|
+
console.error(` Expected: ${expectedHash}`);
|
|
96
|
+
console.error(` Actual: ${actualHash}`);
|
|
97
|
+
process.exit(1);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
console.log("Checksum verified.");
|
|
101
|
+
|
|
102
|
+
// Note on cryptographic signature verification:
|
|
103
|
+
// The .sig file contains an ES256 (P-256 ECDSA) signature produced by
|
|
104
|
+
// the release-sign-prod key in our Azure Key Vault. We do NOT verify
|
|
105
|
+
// it in this npm postinstall hook to keep the surface area minimal at
|
|
106
|
+
// first-install time. Trust chain at install:
|
|
107
|
+
//
|
|
108
|
+
// 1. HTTPS to github.com → authenticates the source
|
|
109
|
+
// 2. SHA-256 of archive → verifies download integrity
|
|
110
|
+
// 3. npm provenance → attests this package was published via
|
|
111
|
+
// our GitHub Actions release workflow
|
|
112
|
+
//
|
|
113
|
+
// After install, the binary's `twira update` flow performs full ES256
|
|
114
|
+
// verification against the embedded public key in keys/release.pub.
|
|
115
|
+
// Every subsequent update IS cryptographically verified.
|
|
116
|
+
//
|
|
117
|
+
// To verify the .sig at install time yourself, see SECURITY.md.
|
|
118
|
+
if (sigFile) {
|
|
119
|
+
console.log("ES256 signature available alongside (downloadable from GitHub Release page).");
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return { archive, ext };
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Like fetch() but returns null on 404 / missing instead of throwing.
|
|
127
|
+
* Used for the optional .sig sidecar.
|
|
128
|
+
*/
|
|
129
|
+
async function fetchOptional(url) {
|
|
130
|
+
try {
|
|
131
|
+
return await fetch(url);
|
|
132
|
+
} catch (_err) {
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function extractBinary(archive, ext) {
|
|
138
|
+
const binDir = path.join(__dirname, "bin");
|
|
139
|
+
fs.mkdirSync(binDir, { recursive: true });
|
|
140
|
+
|
|
141
|
+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "twira-"));
|
|
142
|
+
const archivePath = path.join(tmpDir, `archive.${ext}`);
|
|
143
|
+
fs.writeFileSync(archivePath, archive);
|
|
144
|
+
|
|
145
|
+
try {
|
|
146
|
+
if (ext === "tar.gz") {
|
|
147
|
+
execSync(`tar xzf "${archivePath}" -C "${binDir}"`, { stdio: "pipe" });
|
|
148
|
+
} else if (ext === "zip") {
|
|
149
|
+
// Windows: use PowerShell to extract
|
|
150
|
+
execSync(
|
|
151
|
+
`powershell -Command "Expand-Archive -Path '${archivePath}' -DestinationPath '${binDir}' -Force"`,
|
|
152
|
+
{ stdio: "pipe" }
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
} finally {
|
|
156
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const binaryPath = path.join(binDir, process.platform === "win32" ? `${BINARY_NAME}.exe` : BINARY_NAME);
|
|
160
|
+
|
|
161
|
+
if (!fs.existsSync(binaryPath)) {
|
|
162
|
+
console.error(`Binary not found at ${binaryPath} after extraction.`);
|
|
163
|
+
process.exit(1);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (process.platform !== "win32") {
|
|
167
|
+
fs.chmodSync(binaryPath, 0o755);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
console.log(`Installed: ${binaryPath}`);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
async function main() {
|
|
174
|
+
// Skip install in CI if TWIRA_SKIP_INSTALL is set
|
|
175
|
+
if (process.env.TWIRA_SKIP_INSTALL) {
|
|
176
|
+
console.log("TWIRA_SKIP_INSTALL set, skipping binary download.");
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const target = getPlatformTarget();
|
|
181
|
+
const version = getVersion();
|
|
182
|
+
const { archive, ext } = await downloadAndVerify(version, target);
|
|
183
|
+
extractBinary(archive, ext);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
main().catch((err) => {
|
|
187
|
+
console.error(`Failed to install Twira: ${err.message}`);
|
|
188
|
+
process.exit(1);
|
|
189
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@twira/cli",
|
|
3
|
+
"version": "2.0.1",
|
|
4
|
+
"description": "Power tools for AI agents",
|
|
5
|
+
"license": "LicenseRef-Proprietary",
|
|
6
|
+
"homepage": "https://twira.com",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/TwiraHQ/twira.git"
|
|
10
|
+
},
|
|
11
|
+
"bin": {
|
|
12
|
+
"twira": "bin/twira.js"
|
|
13
|
+
},
|
|
14
|
+
"scripts": {
|
|
15
|
+
"postinstall": "node install.js"
|
|
16
|
+
},
|
|
17
|
+
"os": [
|
|
18
|
+
"darwin",
|
|
19
|
+
"linux",
|
|
20
|
+
"win32"
|
|
21
|
+
],
|
|
22
|
+
"cpu": [
|
|
23
|
+
"x64",
|
|
24
|
+
"arm64"
|
|
25
|
+
],
|
|
26
|
+
"keywords": [
|
|
27
|
+
"twira",
|
|
28
|
+
"code-intelligence",
|
|
29
|
+
"mcp",
|
|
30
|
+
"ai-coding",
|
|
31
|
+
"static-analysis",
|
|
32
|
+
"security",
|
|
33
|
+
"blast-radius"
|
|
34
|
+
],
|
|
35
|
+
"files": [
|
|
36
|
+
"install.js",
|
|
37
|
+
"bin/twira.js",
|
|
38
|
+
"LICENSE.md"
|
|
39
|
+
]
|
|
40
|
+
}
|