aioffice 1.6.0
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/README.md +112 -0
- package/bin/aioffice.js +78 -0
- package/install.js +239 -0
- package/package.json +43 -0
- package/platform.js +67 -0
package/README.md
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# aioffice
|
|
2
|
+
|
|
3
|
+
**AIOffice** is an AI-native command-line tool and [MCP](https://modelcontextprotocol.io)
|
|
4
|
+
server for working with real Office documents — `.docx`, `.xlsx`, and `.pptx`.
|
|
5
|
+
It creates, reads, queries, edits, renders, validates, and converts Office files
|
|
6
|
+
through a single stable JSON surface designed for AI agents and scripts.
|
|
7
|
+
|
|
8
|
+
This npm package is a thin installer: on install it downloads the correct
|
|
9
|
+
self-contained native binary for your platform from the official
|
|
10
|
+
[GitHub release](https://github.com/onecer/AIOffice/releases) and verifies it
|
|
11
|
+
against the release's `SHA256SUMS`. There are no other dependencies.
|
|
12
|
+
|
|
13
|
+
## Install
|
|
14
|
+
|
|
15
|
+
Global install (adds an `aioffice` command to your PATH):
|
|
16
|
+
|
|
17
|
+
```sh
|
|
18
|
+
npm install -g aioffice
|
|
19
|
+
aioffice version
|
|
20
|
+
aioffice doctor
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Or run on demand without installing, via `npx`:
|
|
24
|
+
|
|
25
|
+
```sh
|
|
26
|
+
npx aioffice doctor
|
|
27
|
+
npx aioffice create report.docx --title "Q3 Report"
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
> On the first run, `npx` will download and SHA256-verify the native binary if
|
|
31
|
+
> the postinstall step did not already do so.
|
|
32
|
+
|
|
33
|
+
## Use as an MCP server
|
|
34
|
+
|
|
35
|
+
AIOffice speaks MCP over stdio. Point your MCP client at the `aioffice mcp`
|
|
36
|
+
command:
|
|
37
|
+
|
|
38
|
+
```json
|
|
39
|
+
{
|
|
40
|
+
"mcpServers": {
|
|
41
|
+
"aioffice": {
|
|
42
|
+
"command": "aioffice",
|
|
43
|
+
"args": ["mcp"]
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
If you installed globally, `aioffice` resolves from your PATH. If you prefer not
|
|
50
|
+
to install globally, use `npx` as the command instead:
|
|
51
|
+
|
|
52
|
+
```json
|
|
53
|
+
{
|
|
54
|
+
"mcpServers": {
|
|
55
|
+
"aioffice": {
|
|
56
|
+
"command": "npx",
|
|
57
|
+
"args": ["-y", "aioffice", "mcp"]
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
The MCP transport is plain stdio JSON-RPC; the npm shim passes stdin/stdout
|
|
64
|
+
through transparently.
|
|
65
|
+
|
|
66
|
+
## How installation works
|
|
67
|
+
|
|
68
|
+
1. `install.js` (run as a postinstall script) detects your platform and CPU
|
|
69
|
+
architecture and maps them to the matching release asset:
|
|
70
|
+
|
|
71
|
+
| OS / arch | Asset |
|
|
72
|
+
| ---------------- | -------------------------- |
|
|
73
|
+
| macOS arm64 | `aioffice-mac-arm64` |
|
|
74
|
+
| macOS x64 | `aioffice-mac-x64` |
|
|
75
|
+
| Linux x64 | `aioffice-linux-x64` |
|
|
76
|
+
| Linux arm64 | `aioffice-linux-arm64` |
|
|
77
|
+
| Windows x64 | `aioffice-win-x64.exe` |
|
|
78
|
+
| Windows arm64 | `aioffice-win-arm64.exe` |
|
|
79
|
+
|
|
80
|
+
2. It downloads `SHA256SUMS` and the binary from the release
|
|
81
|
+
`v{package version}`, computes the SHA256 of the download, and **refuses to
|
|
82
|
+
install** if it does not match the published checksum.
|
|
83
|
+
3. On success the binary is placed in this package's `bin/` directory and made
|
|
84
|
+
executable (`chmod +x` on Unix). The install is idempotent — re-running it
|
|
85
|
+
skips work when a verified binary is already present.
|
|
86
|
+
|
|
87
|
+
If the download or verification fails, installation aborts with a clear message
|
|
88
|
+
that includes the direct download URL so you can install the binary manually.
|
|
89
|
+
|
|
90
|
+
## Environment overrides
|
|
91
|
+
|
|
92
|
+
Both are useful for testing or for serving binaries from a private mirror:
|
|
93
|
+
|
|
94
|
+
| Variable | Default | Purpose |
|
|
95
|
+
| ---------------------------- | ---------------------------------------------------------------- | ---------------------------------------- |
|
|
96
|
+
| `AIOFFICE_DOWNLOAD_VERSION` | `v{package version}` (e.g. `v1.6.0`) | Release tag to download. |
|
|
97
|
+
| `AIOFFICE_DOWNLOAD_BASEURL` | `https://github.com/onecer/AIOffice/releases/download` | Base URL for the assets + `SHA256SUMS`. |
|
|
98
|
+
|
|
99
|
+
The binary is fetched from `{BASEURL}/{VERSION}/{asset}` and the checksum file
|
|
100
|
+
from `{BASEURL}/{VERSION}/SHA256SUMS`.
|
|
101
|
+
|
|
102
|
+
```sh
|
|
103
|
+
# Example: install a specific version from a local mirror
|
|
104
|
+
AIOFFICE_DOWNLOAD_VERSION=v1.6.0 \
|
|
105
|
+
AIOFFICE_DOWNLOAD_BASEURL=https://mirror.example.com/aioffice \
|
|
106
|
+
npm install -g aioffice
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## License
|
|
110
|
+
|
|
111
|
+
Apache-2.0. See the [AIOffice repository](https://github.com/onecer/AIOffice)
|
|
112
|
+
for source, full documentation, and the AI-facing surface contract.
|
package/bin/aioffice.js
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
// bin/aioffice.js — thin launcher for the AIOffice native binary.
|
|
5
|
+
//
|
|
6
|
+
// Locates the downloaded binary (bin/aioffice[.exe]). If it is missing (e.g.
|
|
7
|
+
// postinstall was skipped, as `npm` does with --ignore-scripts, or under some
|
|
8
|
+
// `npx` flows), it runs the install step on demand, then spawns the binary
|
|
9
|
+
// with the inherited argv and stdio (full pass-through). The child's exit code
|
|
10
|
+
// is propagated. stdio:'inherit' keeps `aioffice mcp` (stdin/stdout JSON-RPC)
|
|
11
|
+
// transparent.
|
|
12
|
+
|
|
13
|
+
const path = require('path');
|
|
14
|
+
const fs = require('fs');
|
|
15
|
+
const { spawn } = require('child_process');
|
|
16
|
+
|
|
17
|
+
const { binaryName } = require('../platform');
|
|
18
|
+
|
|
19
|
+
const BINARY = path.join(__dirname, binaryName());
|
|
20
|
+
|
|
21
|
+
function spawnBinary() {
|
|
22
|
+
if (!fs.existsSync(BINARY)) {
|
|
23
|
+
process.stderr.write(
|
|
24
|
+
'aioffice: native binary not found after install. ' +
|
|
25
|
+
'See messages above; you may need to reinstall or download it ' +
|
|
26
|
+
'manually from https://github.com/onecer/AIOffice/releases\n',
|
|
27
|
+
);
|
|
28
|
+
process.exit(1);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const child = spawn(BINARY, process.argv.slice(2), { stdio: 'inherit' });
|
|
32
|
+
|
|
33
|
+
child.on('error', (err) => {
|
|
34
|
+
process.stderr.write(`aioffice: failed to launch binary: ${err.message}\n`);
|
|
35
|
+
process.exit(1);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
// Forward termination signals so Ctrl-C / kill reach the native process.
|
|
39
|
+
for (const sig of ['SIGINT', 'SIGTERM', 'SIGHUP']) {
|
|
40
|
+
process.on(sig, () => {
|
|
41
|
+
if (!child.killed) {
|
|
42
|
+
try {
|
|
43
|
+
child.kill(sig);
|
|
44
|
+
} catch (_) {
|
|
45
|
+
/* ignore */
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
child.on('exit', (code, signal) => {
|
|
52
|
+
if (signal) {
|
|
53
|
+
// Re-raise the signal so the parent's exit status reflects it.
|
|
54
|
+
process.kill(process.pid, signal);
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
process.exit(code == null ? 0 : code);
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async function main() {
|
|
62
|
+
if (!fs.existsSync(BINARY)) {
|
|
63
|
+
// Lazy install (postinstall was skipped or `npx` first run).
|
|
64
|
+
process.stderr.write('aioffice: binary not present, installing...\n');
|
|
65
|
+
try {
|
|
66
|
+
const install = require('../install');
|
|
67
|
+
await install.main();
|
|
68
|
+
} catch (err) {
|
|
69
|
+
process.stderr.write(
|
|
70
|
+
(err && err.message ? err.message : String(err)) + '\n',
|
|
71
|
+
);
|
|
72
|
+
process.exit(1);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
spawnBinary();
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
main();
|
package/install.js
ADDED
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// install.js — postinstall step for the "aioffice" npm package.
|
|
4
|
+
//
|
|
5
|
+
// Downloads the platform-specific AIOffice binary from the matching GitHub
|
|
6
|
+
// release, verifies it against the release's SHA256SUMS file, and installs it
|
|
7
|
+
// (executable) into bin/. Uses ONLY Node builtins (https, fs, path, crypto) —
|
|
8
|
+
// no third-party dependencies.
|
|
9
|
+
//
|
|
10
|
+
// Environment overrides (handy for testing + private mirrors):
|
|
11
|
+
// AIOFFICE_DOWNLOAD_VERSION — release tag to download (default: v{package version})
|
|
12
|
+
// AIOFFICE_DOWNLOAD_BASEURL — base URL for the release assets
|
|
13
|
+
// (default: https://github.com/onecer/AIOffice/releases/download)
|
|
14
|
+
//
|
|
15
|
+
// The full asset URL is: {BASEURL}/{VERSION}/{asset}
|
|
16
|
+
// and the checksum file: {BASEURL}/{VERSION}/SHA256SUMS
|
|
17
|
+
|
|
18
|
+
const fs = require('fs');
|
|
19
|
+
const path = require('path');
|
|
20
|
+
const https = require('https');
|
|
21
|
+
const crypto = require('crypto');
|
|
22
|
+
|
|
23
|
+
const { assetName, binaryName } = require('./platform');
|
|
24
|
+
const pkg = require('./package.json');
|
|
25
|
+
|
|
26
|
+
const DEFAULT_BASEURL = 'https://github.com/onecer/AIOffice/releases/download';
|
|
27
|
+
const REPO_RELEASES = 'https://github.com/onecer/AIOffice/releases';
|
|
28
|
+
|
|
29
|
+
const BIN_DIR = path.join(__dirname, 'bin');
|
|
30
|
+
|
|
31
|
+
// Resolve the release tag. A bare package version "1.6.0" becomes "v1.6.0";
|
|
32
|
+
// an explicit override is used verbatim (it may or may not carry a leading v).
|
|
33
|
+
function resolveVersion() {
|
|
34
|
+
const override = process.env.AIOFFICE_DOWNLOAD_VERSION;
|
|
35
|
+
if (override && override.trim()) return override.trim();
|
|
36
|
+
return `v${pkg.version}`;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function resolveBaseUrl() {
|
|
40
|
+
const override = process.env.AIOFFICE_DOWNLOAD_BASEURL;
|
|
41
|
+
if (override && override.trim()) return override.trim().replace(/\/+$/, '');
|
|
42
|
+
return DEFAULT_BASEURL;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// GET a URL into a Buffer, following redirects (GitHub asset URLs 302 to a CDN).
|
|
46
|
+
function fetchBuffer(url, redirectsLeft = 5) {
|
|
47
|
+
return new Promise((resolve, reject) => {
|
|
48
|
+
const req = https.get(
|
|
49
|
+
url,
|
|
50
|
+
{ headers: { 'User-Agent': `aioffice-npm/${pkg.version}`, Accept: '*/*' } },
|
|
51
|
+
(res) => {
|
|
52
|
+
const { statusCode, headers } = res;
|
|
53
|
+
if (statusCode >= 300 && statusCode < 400 && headers.location) {
|
|
54
|
+
res.resume(); // drain
|
|
55
|
+
if (redirectsLeft <= 0) {
|
|
56
|
+
reject(new Error(`Too many redirects fetching ${url}`));
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
const next = new URL(headers.location, url).toString();
|
|
60
|
+
resolve(fetchBuffer(next, redirectsLeft - 1));
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
if (statusCode !== 200) {
|
|
64
|
+
res.resume();
|
|
65
|
+
reject(
|
|
66
|
+
new Error(
|
|
67
|
+
`Download failed (HTTP ${statusCode}) for ${url}`,
|
|
68
|
+
),
|
|
69
|
+
);
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
const chunks = [];
|
|
73
|
+
res.on('data', (c) => chunks.push(c));
|
|
74
|
+
res.on('end', () => resolve(Buffer.concat(chunks)));
|
|
75
|
+
},
|
|
76
|
+
);
|
|
77
|
+
req.on('error', reject);
|
|
78
|
+
req.setTimeout(120000, () => {
|
|
79
|
+
req.destroy(new Error(`Download timed out after 120s for ${url}`));
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function sha256(buf) {
|
|
85
|
+
return crypto.createHash('sha256').update(buf).digest('hex');
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Parse a sha256sum-style file: lines of "<hex>␠␠<filename>".
|
|
89
|
+
// Returns a Map of filename -> lowercase hex digest.
|
|
90
|
+
function parseSha256Sums(text) {
|
|
91
|
+
const map = new Map();
|
|
92
|
+
for (const raw of text.split(/\r?\n/)) {
|
|
93
|
+
const line = raw.trim();
|
|
94
|
+
if (!line) continue;
|
|
95
|
+
const m = line.match(/^([0-9a-fA-F]{64})[\s*]+(.+)$/);
|
|
96
|
+
if (!m) continue;
|
|
97
|
+
map.set(m[2].trim(), m[1].toLowerCase());
|
|
98
|
+
}
|
|
99
|
+
return map;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async function main() {
|
|
103
|
+
const version = resolveVersion();
|
|
104
|
+
const baseUrl = resolveBaseUrl();
|
|
105
|
+
const asset = assetName(); // throws a clear error on unsupported platform
|
|
106
|
+
const localName = binaryName();
|
|
107
|
+
const dest = path.join(BIN_DIR, localName);
|
|
108
|
+
|
|
109
|
+
const assetUrl = `${baseUrl}/${version}/${asset}`;
|
|
110
|
+
const sumsUrl = `${baseUrl}/${version}/SHA256SUMS`;
|
|
111
|
+
|
|
112
|
+
fs.mkdirSync(BIN_DIR, { recursive: true });
|
|
113
|
+
|
|
114
|
+
// --- Idempotency: skip if a correct binary is already present. -----------
|
|
115
|
+
// We need the expected digest to know it's correct, so fetch SHA256SUMS
|
|
116
|
+
// first. If the network is unavailable but a binary already exists, trust it
|
|
117
|
+
// (a prior install verified it) rather than failing a re-run.
|
|
118
|
+
let sums;
|
|
119
|
+
try {
|
|
120
|
+
const sumsBuf = await fetchBuffer(sumsUrl);
|
|
121
|
+
sums = parseSha256Sums(sumsBuf.toString('utf8'));
|
|
122
|
+
} catch (err) {
|
|
123
|
+
if (fs.existsSync(dest) && fs.statSync(dest).size > 0) {
|
|
124
|
+
console.error(
|
|
125
|
+
`aioffice: could not fetch SHA256SUMS (${err.message}); ` +
|
|
126
|
+
'a binary is already installed, keeping it.',
|
|
127
|
+
);
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
fail(
|
|
131
|
+
`Could not download the checksum file.\n ${err.message}`,
|
|
132
|
+
version,
|
|
133
|
+
asset,
|
|
134
|
+
assetUrl,
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const expected = sums.get(asset);
|
|
139
|
+
if (!expected) {
|
|
140
|
+
fail(
|
|
141
|
+
`Release ${version} has no checksum entry for "${asset}".\n` +
|
|
142
|
+
` Known entries: ${[...sums.keys()].join(', ') || '(none)'}`,
|
|
143
|
+
version,
|
|
144
|
+
asset,
|
|
145
|
+
assetUrl,
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (fs.existsSync(dest) && fs.statSync(dest).size > 0) {
|
|
150
|
+
const have = sha256(fs.readFileSync(dest));
|
|
151
|
+
if (have === expected) {
|
|
152
|
+
ensureExecutable(dest);
|
|
153
|
+
console.log(
|
|
154
|
+
`aioffice: ${localName} already installed and verified (${version}).`,
|
|
155
|
+
);
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
// Present but wrong (partial/stale download) — re-download below.
|
|
159
|
+
console.error('aioffice: existing binary failed verification, re-downloading.');
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// --- Download the binary. -------------------------------------------------
|
|
163
|
+
console.log(`aioffice: downloading ${asset} (${version})...`);
|
|
164
|
+
let binBuf;
|
|
165
|
+
try {
|
|
166
|
+
binBuf = await fetchBuffer(assetUrl);
|
|
167
|
+
} catch (err) {
|
|
168
|
+
fail(
|
|
169
|
+
`Could not download the binary.\n ${err.message}`,
|
|
170
|
+
version,
|
|
171
|
+
asset,
|
|
172
|
+
assetUrl,
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// --- Verify SHA256 BEFORE writing the final file. ------------------------
|
|
177
|
+
const actual = sha256(binBuf);
|
|
178
|
+
if (actual !== expected) {
|
|
179
|
+
fail(
|
|
180
|
+
'SHA256 checksum mismatch — the download may be corrupt or tampered ' +
|
|
181
|
+
'with. Nothing was installed.\n' +
|
|
182
|
+
` expected: ${expected}\n actual: ${actual}`,
|
|
183
|
+
version,
|
|
184
|
+
asset,
|
|
185
|
+
assetUrl,
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Write atomically: temp file -> rename.
|
|
190
|
+
const tmp = `${dest}.download-${process.pid}`;
|
|
191
|
+
fs.writeFileSync(tmp, binBuf);
|
|
192
|
+
fs.renameSync(tmp, dest);
|
|
193
|
+
ensureExecutable(dest);
|
|
194
|
+
|
|
195
|
+
console.log(
|
|
196
|
+
`aioffice: installed ${localName} (${version}), SHA256 verified.`,
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function ensureExecutable(file) {
|
|
201
|
+
if (process.platform !== 'win32') {
|
|
202
|
+
try {
|
|
203
|
+
fs.chmodSync(file, 0o755);
|
|
204
|
+
} catch (_) {
|
|
205
|
+
/* best-effort */
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function fail(message, version, asset, assetUrl) {
|
|
211
|
+
const lines = [
|
|
212
|
+
'',
|
|
213
|
+
'aioffice: installation failed.',
|
|
214
|
+
` ${message.replace(/\n/g, '\n ')}`,
|
|
215
|
+
'',
|
|
216
|
+
'You can download the binary manually from:',
|
|
217
|
+
` ${assetUrl}`,
|
|
218
|
+
` (release page: ${REPO_RELEASES}/tag/${version})`,
|
|
219
|
+
'then place it in this package\'s bin/ directory as ' +
|
|
220
|
+
`"${binaryName()}"` +
|
|
221
|
+
(process.platform === 'win32' ? '.' : ' and run chmod +x on it.'),
|
|
222
|
+
'',
|
|
223
|
+
'Env overrides: AIOFFICE_DOWNLOAD_VERSION, AIOFFICE_DOWNLOAD_BASEURL.',
|
|
224
|
+
'',
|
|
225
|
+
];
|
|
226
|
+
const err = new Error(lines.join('\n'));
|
|
227
|
+
err.handled = true;
|
|
228
|
+
throw err;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Allow `require('./install')` (used by the bin shim) to call main() directly.
|
|
232
|
+
module.exports = { main, parseSha256Sums, resolveVersion, resolveBaseUrl };
|
|
233
|
+
|
|
234
|
+
if (require.main === module) {
|
|
235
|
+
main().catch((err) => {
|
|
236
|
+
process.stderr.write((err && err.message ? err.message : String(err)) + '\n');
|
|
237
|
+
process.exit(1);
|
|
238
|
+
});
|
|
239
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "aioffice",
|
|
3
|
+
"version": "1.6.0",
|
|
4
|
+
"description": "AIOffice — an AI-native CLI + MCP server for creating, reading, editing, rendering and validating real Office documents (.docx/.xlsx/.pptx). Installs a single self-contained native binary downloaded and SHA256-verified from the GitHub release.",
|
|
5
|
+
"bin": {
|
|
6
|
+
"aioffice": "bin/aioffice.js"
|
|
7
|
+
},
|
|
8
|
+
"scripts": {
|
|
9
|
+
"postinstall": "node install.js"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"bin/aioffice.js",
|
|
13
|
+
"install.js",
|
|
14
|
+
"platform.js"
|
|
15
|
+
],
|
|
16
|
+
"engines": {
|
|
17
|
+
"node": ">=18"
|
|
18
|
+
},
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "git+https://github.com/onecer/AIOffice.git"
|
|
22
|
+
},
|
|
23
|
+
"homepage": "https://github.com/onecer/AIOffice#readme",
|
|
24
|
+
"bugs": {
|
|
25
|
+
"url": "https://github.com/onecer/AIOffice/issues"
|
|
26
|
+
},
|
|
27
|
+
"keywords": [
|
|
28
|
+
"office",
|
|
29
|
+
"docx",
|
|
30
|
+
"xlsx",
|
|
31
|
+
"pptx",
|
|
32
|
+
"ooxml",
|
|
33
|
+
"mcp",
|
|
34
|
+
"ai",
|
|
35
|
+
"cli",
|
|
36
|
+
"word",
|
|
37
|
+
"excel",
|
|
38
|
+
"powerpoint",
|
|
39
|
+
"automation"
|
|
40
|
+
],
|
|
41
|
+
"license": "Apache-2.0",
|
|
42
|
+
"author": "onecer"
|
|
43
|
+
}
|
package/platform.js
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// platform.js — maps the running platform to the GitHub release asset name and
|
|
4
|
+
// to the local binary filename. This is the single source of truth for the
|
|
5
|
+
// platform mapping; install.js and bin/aioffice.js both import it.
|
|
6
|
+
//
|
|
7
|
+
// Asset map (must match the names of the assets attached to each GitHub
|
|
8
|
+
// release v{version}):
|
|
9
|
+
// darwin + arm64 -> aioffice-mac-arm64
|
|
10
|
+
// darwin + x64 -> aioffice-mac-x64
|
|
11
|
+
// linux + x64 -> aioffice-linux-x64
|
|
12
|
+
// linux + arm64 -> aioffice-linux-arm64
|
|
13
|
+
// win32 + x64 -> aioffice-win-x64.exe
|
|
14
|
+
// win32 + arm64 -> aioffice-win-arm64.exe
|
|
15
|
+
|
|
16
|
+
// platform -> arch -> release asset name
|
|
17
|
+
const ASSETS = {
|
|
18
|
+
darwin: {
|
|
19
|
+
arm64: 'aioffice-mac-arm64',
|
|
20
|
+
x64: 'aioffice-mac-x64',
|
|
21
|
+
},
|
|
22
|
+
linux: {
|
|
23
|
+
x64: 'aioffice-linux-x64',
|
|
24
|
+
arm64: 'aioffice-linux-arm64',
|
|
25
|
+
},
|
|
26
|
+
win32: {
|
|
27
|
+
x64: 'aioffice-win-x64.exe',
|
|
28
|
+
arm64: 'aioffice-win-arm64.exe',
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* The local filename the downloaded binary is stored under, inside bin/.
|
|
34
|
+
* Windows needs a .exe extension so the OS will execute it; every other
|
|
35
|
+
* platform uses a plain "aioffice".
|
|
36
|
+
* @param {string} [platform=process.platform]
|
|
37
|
+
* @returns {string}
|
|
38
|
+
*/
|
|
39
|
+
function binaryName(platform = process.platform) {
|
|
40
|
+
return platform === 'win32' ? 'aioffice.exe' : 'aioffice';
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Resolve the GitHub release asset name for the given platform/arch.
|
|
45
|
+
* Throws a clear, actionable error on an unsupported combination.
|
|
46
|
+
* @param {string} [platform=process.platform]
|
|
47
|
+
* @param {string} [arch=process.arch]
|
|
48
|
+
* @returns {string} the release asset filename
|
|
49
|
+
*/
|
|
50
|
+
function assetName(platform = process.platform, arch = process.arch) {
|
|
51
|
+
const byArch = ASSETS[platform];
|
|
52
|
+
const asset = byArch && byArch[arch];
|
|
53
|
+
if (!asset) {
|
|
54
|
+
const supported = Object.entries(ASSETS)
|
|
55
|
+
.flatMap(([p, m]) => Object.keys(m).map((a) => `${p}/${a}`))
|
|
56
|
+
.join(', ');
|
|
57
|
+
throw new Error(
|
|
58
|
+
`aioffice: unsupported platform "${platform}/${arch}". ` +
|
|
59
|
+
`Supported: ${supported}. ` +
|
|
60
|
+
'If you believe this platform should be supported, please open an ' +
|
|
61
|
+
'issue at https://github.com/onecer/AIOffice/issues',
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
return asset;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
module.exports = { ASSETS, assetName, binaryName };
|