@cloneisyou/cli 0.1.4-win32-x64 → 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.
- package/LICENSE.md +23 -23
- package/README.md +88 -88
- package/bin/clone.js +29 -0
- package/package.json +36 -8
- package/scripts/install-native.js +13 -0
- package/scripts/native.js +244 -0
- package/scripts/sync-platform-deps.js +18 -0
- package/scripts/update-check.js +132 -0
- package/vendor/win32-x64/clone/clone.exe +0 -0
package/LICENSE.md
CHANGED
|
@@ -1,23 +1,23 @@
|
|
|
1
|
-
# Clone CLI Proprietary License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2026 Clone Labs, Inc. All rights reserved.
|
|
4
|
-
|
|
5
|
-
This package and its contents are proprietary to Clone Labs, Inc.
|
|
6
|
-
|
|
7
|
-
Permission is granted only to Clone Labs, Inc., its employees, contractors, and
|
|
8
|
-
other persons or entities expressly authorized in writing by Clone Labs, Inc. to
|
|
9
|
-
install, copy, run, evaluate, modify, or distribute this package.
|
|
10
|
-
|
|
11
|
-
No other license is granted. Without prior written permission from Clone Labs,
|
|
12
|
-
Inc., you may not use, copy, modify, publish, distribute, sublicense, sell, host,
|
|
13
|
-
make available as a service, reverse engineer, or create derivative works based
|
|
14
|
-
on this package or any portion of it.
|
|
15
|
-
|
|
16
|
-
Public availability of this package in a package registry does not grant public
|
|
17
|
-
usage rights.
|
|
18
|
-
|
|
19
|
-
THE PACKAGE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
20
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
|
21
|
-
FOR A PARTICULAR PURPOSE, AND NONINFRINGEMENT. IN NO EVENT SHALL CLONE LABS,
|
|
22
|
-
INC. BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY ARISING FROM OR
|
|
23
|
-
RELATED TO THE PACKAGE.
|
|
1
|
+
# Clone CLI Proprietary License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Clone Labs, Inc. All rights reserved.
|
|
4
|
+
|
|
5
|
+
This package and its contents are proprietary to Clone Labs, Inc.
|
|
6
|
+
|
|
7
|
+
Permission is granted only to Clone Labs, Inc., its employees, contractors, and
|
|
8
|
+
other persons or entities expressly authorized in writing by Clone Labs, Inc. to
|
|
9
|
+
install, copy, run, evaluate, modify, or distribute this package.
|
|
10
|
+
|
|
11
|
+
No other license is granted. Without prior written permission from Clone Labs,
|
|
12
|
+
Inc., you may not use, copy, modify, publish, distribute, sublicense, sell, host,
|
|
13
|
+
make available as a service, reverse engineer, or create derivative works based
|
|
14
|
+
on this package or any portion of it.
|
|
15
|
+
|
|
16
|
+
Public availability of this package in a package registry does not grant public
|
|
17
|
+
usage rights.
|
|
18
|
+
|
|
19
|
+
THE PACKAGE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
20
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
|
21
|
+
FOR A PARTICULAR PURPOSE, AND NONINFRINGEMENT. IN NO EVENT SHALL CLONE LABS,
|
|
22
|
+
INC. BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY ARISING FROM OR
|
|
23
|
+
RELATED TO THE PACKAGE.
|
package/README.md
CHANGED
|
@@ -1,88 +1,88 @@
|
|
|
1
|
-
# Clone CLI
|
|
2
|
-
|
|
3
|
-
Native npm launcher for the Clone CLI.
|
|
4
|
-
|
|
5
|
-
Status: early alpha. This package is proprietary to Clone Labs, Inc. and
|
|
6
|
-
licensed for Clone Labs, Inc. internal/company use only. Public registry
|
|
7
|
-
visibility does not grant public usage, modification, redistribution, or
|
|
8
|
-
commercial rights.
|
|
9
|
-
|
|
10
|
-
## Install
|
|
11
|
-
|
|
12
|
-
```sh
|
|
13
|
-
npm install -g @cloneisyou/cli
|
|
14
|
-
clone
|
|
15
|
-
```
|
|
16
|
-
|
|
17
|
-
## Distribution Model
|
|
18
|
-
|
|
19
|
-
This package follows the native-launcher pattern used by tools such as Claude
|
|
20
|
-
Code: the public npm package contains only a small JavaScript launcher and
|
|
21
|
-
installer. It does not bundle Clone Labs, Inc.'s proprietary Python source.
|
|
22
|
-
|
|
23
|
-
At runtime, the launcher resolves the native `clone` executable in this order:
|
|
24
|
-
|
|
25
|
-
1. `CLONE_NATIVE_BINARY`, for internal development or manual installs.
|
|
26
|
-
2. A matching npm optional dependency alias, such as
|
|
27
|
-
`@cloneisyou/cli-win32-x64`, which points to
|
|
28
|
-
`@cloneisyou/cli@<version>-win32-x64`.
|
|
29
|
-
3. A private artifact mirror, but only when `CLONE_DOWNLOAD_BASE_URL` is set
|
|
30
|
-
explicitly.
|
|
31
|
-
|
|
32
|
-
Clone Labs, Inc.'s GitHub repository and GitHub Releases are private. Public
|
|
33
|
-
npm installs do not depend on GitHub Releases or GitHub Actions artifacts. The
|
|
34
|
-
release workflow builds each native binary on its matching runner and publishes
|
|
35
|
-
that platform package directly to npm before publishing the main launcher.
|
|
36
|
-
|
|
37
|
-
The package metadata links to the private Clone Labs, Inc. repository and issue
|
|
38
|
-
tracker. Access requires repository permissions.
|
|
39
|
-
|
|
40
|
-
Expected private artifact names when `CLONE_DOWNLOAD_BASE_URL` is used:
|
|
41
|
-
|
|
42
|
-
- `clone-darwin-arm64`
|
|
43
|
-
- `clone-darwin-x64`
|
|
44
|
-
- `clone-linux-x64`
|
|
45
|
-
- `clone-win32-x64.exe`
|
|
46
|
-
|
|
47
|
-
If a `manifest.json` is present at the release URL, the installer will use
|
|
48
|
-
`platforms.<platform>.url` and verify `sha256` or `checksum` when provided.
|
|
49
|
-
|
|
50
|
-
## Requirements
|
|
51
|
-
|
|
52
|
-
- Node.js 18+
|
|
53
|
-
- A supported platform: macOS arm64/x64, Linux x64, or Windows x64.
|
|
54
|
-
|
|
55
|
-
Python is not required by this npm package once native binaries are published.
|
|
56
|
-
|
|
57
|
-
## Local Data And Keys
|
|
58
|
-
|
|
59
|
-
Clone stores local state under `~/.clone` by default. This includes session
|
|
60
|
-
metadata, local memory/soul files, and optional context screenshots.
|
|
61
|
-
|
|
62
|
-
If you run `clone login`, provider API keys are stored locally in:
|
|
63
|
-
|
|
64
|
-
```text
|
|
65
|
-
~/.clone/secrets.json
|
|
66
|
-
```
|
|
67
|
-
|
|
68
|
-
Environment variables such as `ANTHROPIC_API_KEY` and `OPENAI_API_KEY` take
|
|
69
|
-
precedence over locally stored keys.
|
|
70
|
-
|
|
71
|
-
## Configuration
|
|
72
|
-
|
|
73
|
-
Useful environment variables:
|
|
74
|
-
|
|
75
|
-
- `CLONE_NATIVE_BINARY`: explicit path to a native `clone` executable.
|
|
76
|
-
- `CLONE_DOWNLOAD_BASE_URL`: explicit private artifact base URL for internal testing.
|
|
77
|
-
- `CLONE_NPM_RUNTIME`: override the native binary cache directory.
|
|
78
|
-
- `CLONE_HOME`: override Clone's data directory. Defaults to `~/.clone`.
|
|
79
|
-
- `CLONE_DISABLE_TRAY=1`: disable the desktop tray/menu-bar recording indicator.
|
|
80
|
-
- `CLONE_DISABLE_CONTEXT_WORKER=1`: disable background context screenshots.
|
|
81
|
-
- `CLONE_SKIP_UPDATE_CHECK=1`: skip the npm `latest` version check before launching.
|
|
82
|
-
- `CLONE_SKIP_NATIVE_INSTALL=1`: skip download attempts during install.
|
|
83
|
-
|
|
84
|
-
Development smoke test:
|
|
85
|
-
|
|
86
|
-
```sh
|
|
87
|
-
npm --prefix apps/cli/npm run smoke
|
|
88
|
-
```
|
|
1
|
+
# Clone CLI
|
|
2
|
+
|
|
3
|
+
Native npm launcher for the Clone CLI.
|
|
4
|
+
|
|
5
|
+
Status: early alpha. This package is proprietary to Clone Labs, Inc. and
|
|
6
|
+
licensed for Clone Labs, Inc. internal/company use only. Public registry
|
|
7
|
+
visibility does not grant public usage, modification, redistribution, or
|
|
8
|
+
commercial rights.
|
|
9
|
+
|
|
10
|
+
## Install
|
|
11
|
+
|
|
12
|
+
```sh
|
|
13
|
+
npm install -g @cloneisyou/cli
|
|
14
|
+
clone
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Distribution Model
|
|
18
|
+
|
|
19
|
+
This package follows the native-launcher pattern used by tools such as Claude
|
|
20
|
+
Code: the public npm package contains only a small JavaScript launcher and
|
|
21
|
+
installer. It does not bundle Clone Labs, Inc.'s proprietary Python source.
|
|
22
|
+
|
|
23
|
+
At runtime, the launcher resolves the native `clone` executable in this order:
|
|
24
|
+
|
|
25
|
+
1. `CLONE_NATIVE_BINARY`, for internal development or manual installs.
|
|
26
|
+
2. A matching npm optional dependency alias, such as
|
|
27
|
+
`@cloneisyou/cli-win32-x64`, which points to
|
|
28
|
+
`@cloneisyou/cli@<version>-win32-x64`.
|
|
29
|
+
3. A private artifact mirror, but only when `CLONE_DOWNLOAD_BASE_URL` is set
|
|
30
|
+
explicitly.
|
|
31
|
+
|
|
32
|
+
Clone Labs, Inc.'s GitHub repository and GitHub Releases are private. Public
|
|
33
|
+
npm installs do not depend on GitHub Releases or GitHub Actions artifacts. The
|
|
34
|
+
release workflow builds each native binary on its matching runner and publishes
|
|
35
|
+
that platform package directly to npm before publishing the main launcher.
|
|
36
|
+
|
|
37
|
+
The package metadata links to the private Clone Labs, Inc. repository and issue
|
|
38
|
+
tracker. Access requires repository permissions.
|
|
39
|
+
|
|
40
|
+
Expected private artifact names when `CLONE_DOWNLOAD_BASE_URL` is used:
|
|
41
|
+
|
|
42
|
+
- `clone-darwin-arm64`
|
|
43
|
+
- `clone-darwin-x64`
|
|
44
|
+
- `clone-linux-x64`
|
|
45
|
+
- `clone-win32-x64.exe`
|
|
46
|
+
|
|
47
|
+
If a `manifest.json` is present at the release URL, the installer will use
|
|
48
|
+
`platforms.<platform>.url` and verify `sha256` or `checksum` when provided.
|
|
49
|
+
|
|
50
|
+
## Requirements
|
|
51
|
+
|
|
52
|
+
- Node.js 18+
|
|
53
|
+
- A supported platform: macOS arm64/x64, Linux x64, or Windows x64.
|
|
54
|
+
|
|
55
|
+
Python is not required by this npm package once native binaries are published.
|
|
56
|
+
|
|
57
|
+
## Local Data And Keys
|
|
58
|
+
|
|
59
|
+
Clone stores local state under `~/.clone` by default. This includes session
|
|
60
|
+
metadata, local memory/soul files, and optional context screenshots.
|
|
61
|
+
|
|
62
|
+
If you run `clone login`, provider API keys are stored locally in:
|
|
63
|
+
|
|
64
|
+
```text
|
|
65
|
+
~/.clone/secrets.json
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Environment variables such as `ANTHROPIC_API_KEY` and `OPENAI_API_KEY` take
|
|
69
|
+
precedence over locally stored keys.
|
|
70
|
+
|
|
71
|
+
## Configuration
|
|
72
|
+
|
|
73
|
+
Useful environment variables:
|
|
74
|
+
|
|
75
|
+
- `CLONE_NATIVE_BINARY`: explicit path to a native `clone` executable.
|
|
76
|
+
- `CLONE_DOWNLOAD_BASE_URL`: explicit private artifact base URL for internal testing.
|
|
77
|
+
- `CLONE_NPM_RUNTIME`: override the native binary cache directory.
|
|
78
|
+
- `CLONE_HOME`: override Clone's data directory. Defaults to `~/.clone`.
|
|
79
|
+
- `CLONE_DISABLE_TRAY=1`: disable the desktop tray/menu-bar recording indicator.
|
|
80
|
+
- `CLONE_DISABLE_CONTEXT_WORKER=1`: disable background context screenshots.
|
|
81
|
+
- `CLONE_SKIP_UPDATE_CHECK=1`: skip the npm `latest` version check before launching.
|
|
82
|
+
- `CLONE_SKIP_NATIVE_INSTALL=1`: skip download attempts during install.
|
|
83
|
+
|
|
84
|
+
Development smoke test:
|
|
85
|
+
|
|
86
|
+
```sh
|
|
87
|
+
npm --prefix apps/cli/npm run smoke
|
|
88
|
+
```
|
package/bin/clone.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
|
|
4
|
+
const { spawnSync } = require("node:child_process");
|
|
5
|
+
const { ensureNative } = require("../scripts/native");
|
|
6
|
+
const { checkForUpdate } = require("../scripts/update-check");
|
|
7
|
+
|
|
8
|
+
main().catch((error) => {
|
|
9
|
+
process.stderr.write(`\nClone npm wrapper error:\n${error.message}\n`);
|
|
10
|
+
process.exit(1);
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
async function main() {
|
|
14
|
+
await checkForUpdate();
|
|
15
|
+
|
|
16
|
+
const executable = await ensureNative({ allowDownload: true });
|
|
17
|
+
const result = spawnSync(executable, process.argv.slice(2), {
|
|
18
|
+
stdio: "inherit",
|
|
19
|
+
env: process.env,
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
if (result.error) {
|
|
23
|
+
throw new Error(`failed to run Clone CLI: ${result.error.message}`);
|
|
24
|
+
}
|
|
25
|
+
if (result.signal) {
|
|
26
|
+
process.kill(process.pid, result.signal);
|
|
27
|
+
}
|
|
28
|
+
process.exit(result.status ?? 1);
|
|
29
|
+
}
|
package/package.json
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cloneisyou/cli",
|
|
3
|
-
"version": "0.1.4
|
|
4
|
-
"description": "Native
|
|
3
|
+
"version": "0.1.4",
|
|
4
|
+
"description": "Native launcher for the Clone CLI",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE.md",
|
|
6
|
+
"private": false,
|
|
6
7
|
"author": "Clone Labs, Inc.",
|
|
7
8
|
"homepage": "https://clone.is",
|
|
8
9
|
"repository": {
|
|
@@ -10,17 +11,44 @@
|
|
|
10
11
|
"url": "git+https://github.com/cloneisyou/clone.git",
|
|
11
12
|
"directory": "apps/cli/npm"
|
|
12
13
|
},
|
|
13
|
-
"
|
|
14
|
-
|
|
14
|
+
"bugs": {
|
|
15
|
+
"url": "https://github.com/cloneisyou/clone/issues"
|
|
16
|
+
},
|
|
17
|
+
"keywords": [
|
|
18
|
+
"clone",
|
|
19
|
+
"cli",
|
|
20
|
+
"agent",
|
|
21
|
+
"automation",
|
|
22
|
+
"capture",
|
|
23
|
+
"memory",
|
|
24
|
+
"terminal"
|
|
25
|
+
],
|
|
26
|
+
"publishConfig": {
|
|
27
|
+
"access": "public"
|
|
28
|
+
},
|
|
15
29
|
"bin": {
|
|
16
|
-
"clone": "
|
|
30
|
+
"clone": "bin/clone.js"
|
|
31
|
+
},
|
|
32
|
+
"optionalDependencies": {
|
|
33
|
+
"@cloneisyou/cli-darwin-arm64": "npm:@cloneisyou/cli@0.1.4-darwin-arm64",
|
|
34
|
+
"@cloneisyou/cli-darwin-x64": "npm:@cloneisyou/cli@0.1.4-darwin-x64",
|
|
35
|
+
"@cloneisyou/cli-linux-x64": "npm:@cloneisyou/cli@0.1.4-linux-x64",
|
|
36
|
+
"@cloneisyou/cli-win32-x64": "npm:@cloneisyou/cli@0.1.4-win32-x64"
|
|
17
37
|
},
|
|
18
38
|
"files": [
|
|
19
|
-
"
|
|
39
|
+
"bin",
|
|
40
|
+
"scripts",
|
|
20
41
|
"README.md",
|
|
21
42
|
"LICENSE.md"
|
|
22
43
|
],
|
|
23
|
-
"
|
|
24
|
-
"
|
|
44
|
+
"scripts": {
|
|
45
|
+
"prepack": "node scripts/sync-platform-deps.js",
|
|
46
|
+
"postinstall": "node scripts/install-native.js",
|
|
47
|
+
"sync-platform-deps": "node scripts/sync-platform-deps.js",
|
|
48
|
+
"test": "node --test tests/*.test.js",
|
|
49
|
+
"smoke": "node -e \"const native=require('./scripts/native'); console.log(native.resolvePlatformKey())\" && npm pack --dry-run"
|
|
50
|
+
},
|
|
51
|
+
"engines": {
|
|
52
|
+
"node": ">=18"
|
|
25
53
|
}
|
|
26
54
|
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const { ensureNative } = require("./native");
|
|
4
|
+
|
|
5
|
+
ensureNative({ allowDownload: true, optional: true })
|
|
6
|
+
.then((executable) => {
|
|
7
|
+
if (executable) {
|
|
8
|
+
process.stderr.write(`▮ CLONE native binary ready: ${executable}\n`);
|
|
9
|
+
}
|
|
10
|
+
})
|
|
11
|
+
.catch((error) => {
|
|
12
|
+
process.stderr.write(`▮ CLONE native install skipped: ${error.message}\n`);
|
|
13
|
+
});
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const crypto = require("node:crypto");
|
|
4
|
+
const fs = require("node:fs");
|
|
5
|
+
const https = require("node:https");
|
|
6
|
+
const os = require("node:os");
|
|
7
|
+
const path = require("node:path");
|
|
8
|
+
const { pipeline } = require("node:stream/promises");
|
|
9
|
+
|
|
10
|
+
const packageRoot = path.resolve(__dirname, "..");
|
|
11
|
+
const packageJson = require(path.join(packageRoot, "package.json"));
|
|
12
|
+
const runtimeRoot = path.resolve(
|
|
13
|
+
process.env.CLONE_NPM_RUNTIME ||
|
|
14
|
+
path.join(os.homedir(), ".clone", "npm-runtime", packageJson.version),
|
|
15
|
+
);
|
|
16
|
+
|
|
17
|
+
async function ensureNative({ allowDownload = true, optional = false } = {}) {
|
|
18
|
+
const existing = findNative();
|
|
19
|
+
if (existing) {
|
|
20
|
+
return existing;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (process.env.CLONE_SKIP_NATIVE_INSTALL === "1" || !allowDownload) {
|
|
24
|
+
return failOrNull(missingNativeMessage(), optional);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (!releaseBaseUrl()) {
|
|
28
|
+
return failOrNull(missingNativeMessage(), optional);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
return await downloadNative();
|
|
33
|
+
} catch (error) {
|
|
34
|
+
return failOrNull(`${missingNativeMessage()}\n\nDownload failed: ${error.message}`, optional);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function findNative() {
|
|
39
|
+
const explicit = process.env.CLONE_NATIVE_BINARY;
|
|
40
|
+
if (explicit && isExecutableFile(explicit)) {
|
|
41
|
+
return path.resolve(explicit);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const optionalPackageBinary = findOptionalPackageBinary();
|
|
45
|
+
if (optionalPackageBinary) {
|
|
46
|
+
return optionalPackageBinary;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const cached = cachedBinaryPath();
|
|
50
|
+
if (isExecutableFile(cached)) {
|
|
51
|
+
return cached;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function findOptionalPackageBinary() {
|
|
58
|
+
const packageName = `@cloneisyou/cli-${resolvePlatformKey()}`;
|
|
59
|
+
try {
|
|
60
|
+
const manifestPath = require.resolve(`${packageName}/package.json`, {
|
|
61
|
+
paths: [packageRoot],
|
|
62
|
+
});
|
|
63
|
+
const manifest = require(manifestPath);
|
|
64
|
+
const binField = typeof manifest.bin === "string" ? manifest.bin : manifest.bin?.clone;
|
|
65
|
+
if (!binField) {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
const candidate = path.resolve(path.dirname(manifestPath), binField);
|
|
69
|
+
return isExecutableFile(candidate) ? candidate : null;
|
|
70
|
+
} catch {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async function downloadNative() {
|
|
76
|
+
const platform = resolvePlatformKey();
|
|
77
|
+
const manifest = await fetchManifest().catch(() => null);
|
|
78
|
+
const entry = manifest?.platforms?.[platform];
|
|
79
|
+
const downloadUrl = entry?.url || buildDefaultBinaryUrl(platform);
|
|
80
|
+
const expectedSha256 = entry?.sha256 || entry?.checksum || null;
|
|
81
|
+
const destination = cachedBinaryPath();
|
|
82
|
+
const temp = `${destination}.tmp-${process.pid}`;
|
|
83
|
+
|
|
84
|
+
fs.mkdirSync(path.dirname(destination), { recursive: true });
|
|
85
|
+
log(`installing native Clone CLI for ${platform}`);
|
|
86
|
+
await downloadFile(downloadUrl, temp);
|
|
87
|
+
|
|
88
|
+
if (expectedSha256) {
|
|
89
|
+
const actual = sha256File(temp);
|
|
90
|
+
if (actual.toLowerCase() !== expectedSha256.toLowerCase()) {
|
|
91
|
+
safeUnlink(temp);
|
|
92
|
+
throw new Error(`checksum mismatch for ${downloadUrl}`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
fs.renameSync(temp, destination);
|
|
97
|
+
if (process.platform !== "win32") {
|
|
98
|
+
fs.chmodSync(destination, 0o755);
|
|
99
|
+
}
|
|
100
|
+
return destination;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async function fetchManifest() {
|
|
104
|
+
const baseUrl = releaseBaseUrl();
|
|
105
|
+
const manifestUrl = `${baseUrl.replace(/\/$/, "")}/manifest.json`;
|
|
106
|
+
const text = await readUrl(manifestUrl);
|
|
107
|
+
return JSON.parse(text);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function buildDefaultBinaryUrl(platform) {
|
|
111
|
+
const baseUrl = releaseBaseUrl().replace(/\/$/, "");
|
|
112
|
+
const extension = process.platform === "win32" ? ".exe" : "";
|
|
113
|
+
return `${baseUrl}/clone-${platform}${extension}`;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function releaseBaseUrl() {
|
|
117
|
+
return process.env.CLONE_DOWNLOAD_BASE_URL || null;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function cachedBinaryPath() {
|
|
121
|
+
return path.join(runtimeRoot, "native", resolvePlatformKey(), binaryName());
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function binaryName() {
|
|
125
|
+
return process.platform === "win32" ? "clone.exe" : "clone";
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function resolvePlatformKey() {
|
|
129
|
+
const platform = process.platform;
|
|
130
|
+
const arch = process.arch;
|
|
131
|
+
const supported = new Set([
|
|
132
|
+
"darwin-arm64",
|
|
133
|
+
"darwin-x64",
|
|
134
|
+
"linux-x64",
|
|
135
|
+
"win32-x64",
|
|
136
|
+
]);
|
|
137
|
+
const key = `${platform}-${arch}`;
|
|
138
|
+
if (!supported.has(key)) {
|
|
139
|
+
throw new Error(`unsupported platform: ${key}`);
|
|
140
|
+
}
|
|
141
|
+
return key;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function isExecutableFile(filePath) {
|
|
145
|
+
try {
|
|
146
|
+
return fs.statSync(filePath).isFile();
|
|
147
|
+
} catch {
|
|
148
|
+
return false;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
async function readUrl(url) {
|
|
153
|
+
return new Promise((resolve, reject) => {
|
|
154
|
+
https
|
|
155
|
+
.get(url, (response) => {
|
|
156
|
+
if (isRedirect(response.statusCode)) {
|
|
157
|
+
readUrl(response.headers.location).then(resolve, reject);
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
if (response.statusCode !== 200) {
|
|
161
|
+
reject(new Error(`${url} returned HTTP ${response.statusCode}`));
|
|
162
|
+
response.resume();
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
response.setEncoding("utf8");
|
|
166
|
+
let body = "";
|
|
167
|
+
response.on("data", (chunk) => {
|
|
168
|
+
body += chunk;
|
|
169
|
+
});
|
|
170
|
+
response.on("end", () => resolve(body));
|
|
171
|
+
})
|
|
172
|
+
.on("error", reject);
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
async function downloadFile(url, destination) {
|
|
177
|
+
await new Promise((resolve, reject) => {
|
|
178
|
+
https
|
|
179
|
+
.get(url, (response) => {
|
|
180
|
+
if (isRedirect(response.statusCode)) {
|
|
181
|
+
downloadFile(response.headers.location, destination).then(resolve, reject);
|
|
182
|
+
response.resume();
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
if (response.statusCode !== 200) {
|
|
186
|
+
reject(new Error(`${url} returned HTTP ${response.statusCode}`));
|
|
187
|
+
response.resume();
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
pipeline(response, fs.createWriteStream(destination)).then(resolve, reject);
|
|
191
|
+
})
|
|
192
|
+
.on("error", reject);
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function isRedirect(statusCode) {
|
|
197
|
+
return [301, 302, 303, 307, 308].includes(statusCode);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function sha256File(filePath) {
|
|
201
|
+
const hash = crypto.createHash("sha256");
|
|
202
|
+
hash.update(fs.readFileSync(filePath));
|
|
203
|
+
return hash.digest("hex");
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function safeUnlink(filePath) {
|
|
207
|
+
try {
|
|
208
|
+
fs.unlinkSync(filePath);
|
|
209
|
+
} catch {
|
|
210
|
+
// Best-effort cleanup only.
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function failOrNull(message, optional) {
|
|
215
|
+
if (optional) {
|
|
216
|
+
log(message);
|
|
217
|
+
return null;
|
|
218
|
+
}
|
|
219
|
+
throw new Error(message);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function missingNativeMessage() {
|
|
223
|
+
return [
|
|
224
|
+
"Clone native binary is not installed.",
|
|
225
|
+
"",
|
|
226
|
+
"This npm package intentionally does not include Clone's proprietary Python source.",
|
|
227
|
+
"Install options:",
|
|
228
|
+
" 1. Reinstall @cloneisyou/cli without --omit=optional so npm can install the matching platform package.",
|
|
229
|
+
" 2. For internal development, set CLONE_NATIVE_BINARY to an existing clone executable.",
|
|
230
|
+
" 3. For private artifact testing, set CLONE_DOWNLOAD_BASE_URL explicitly.",
|
|
231
|
+
].join("\n");
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function log(message) {
|
|
235
|
+
process.stderr.write(`▮ CLONE ${message}\n`);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
module.exports = {
|
|
239
|
+
ensureNative,
|
|
240
|
+
findNative,
|
|
241
|
+
resolvePlatformKey,
|
|
242
|
+
cachedBinaryPath,
|
|
243
|
+
releaseBaseUrl,
|
|
244
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const fs = require("node:fs");
|
|
4
|
+
const path = require("node:path");
|
|
5
|
+
|
|
6
|
+
const packagePath = path.resolve(__dirname, "..", "package.json");
|
|
7
|
+
const packageJson = JSON.parse(fs.readFileSync(packagePath, "utf8"));
|
|
8
|
+
const platforms = ["darwin-arm64", "darwin-x64", "linux-x64", "win32-x64"];
|
|
9
|
+
|
|
10
|
+
packageJson.optionalDependencies = Object.fromEntries(
|
|
11
|
+
platforms.map((platform) => [
|
|
12
|
+
`@cloneisyou/cli-${platform}`,
|
|
13
|
+
`npm:@cloneisyou/cli@${packageJson.version}-${platform}`,
|
|
14
|
+
]),
|
|
15
|
+
);
|
|
16
|
+
|
|
17
|
+
fs.writeFileSync(packagePath, `${JSON.stringify(packageJson, null, 2)}\n`);
|
|
18
|
+
console.error(`synced optional platform dependencies for ${packageJson.version}`);
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const https = require("node:https");
|
|
4
|
+
|
|
5
|
+
const packageJson = require("../package.json");
|
|
6
|
+
const packageName = packageJson.name;
|
|
7
|
+
const defaultTimeoutMs = 750;
|
|
8
|
+
|
|
9
|
+
async function checkForUpdate({
|
|
10
|
+
currentVersion = packageJson.version,
|
|
11
|
+
env = process.env,
|
|
12
|
+
stderr = process.stderr,
|
|
13
|
+
fetchLatestVersion = defaultFetchLatestVersion,
|
|
14
|
+
timeoutMs = defaultTimeoutMs,
|
|
15
|
+
} = {}) {
|
|
16
|
+
if (env.CLONE_SKIP_UPDATE_CHECK === "1") {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
try {
|
|
21
|
+
const latestVersion = await withTimeout(fetchLatestVersion(), timeoutMs);
|
|
22
|
+
if (compareVersions(latestVersion, currentVersion) <= 0) {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
stderr.write(updateMessage(currentVersion, latestVersion));
|
|
26
|
+
} catch {
|
|
27
|
+
// Update checks should never block normal CLI execution.
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function updateMessage(currentVersion, latestVersion) {
|
|
32
|
+
return [
|
|
33
|
+
`[CLONE] update available: ${packageName} ${currentVersion} -> ${latestVersion}`,
|
|
34
|
+
`[CLONE] run: npm install -g ${packageName}@latest`,
|
|
35
|
+
"",
|
|
36
|
+
].join("\n");
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function defaultFetchLatestVersion() {
|
|
40
|
+
const url = `https://registry.npmjs.org/${encodeURIComponent(packageName)}/latest`;
|
|
41
|
+
return readJson(url).then((body) => body.version);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function readJson(url) {
|
|
45
|
+
return new Promise((resolve, reject) => {
|
|
46
|
+
const request = https
|
|
47
|
+
.get(url, (response) => {
|
|
48
|
+
if (isRedirect(response.statusCode)) {
|
|
49
|
+
response.resume();
|
|
50
|
+
readJson(response.headers.location).then(resolve, reject);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
if (response.statusCode !== 200) {
|
|
54
|
+
response.resume();
|
|
55
|
+
reject(new Error(`${url} returned HTTP ${response.statusCode}`));
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
response.setEncoding("utf8");
|
|
60
|
+
let body = "";
|
|
61
|
+
response.on("data", (chunk) => {
|
|
62
|
+
body += chunk;
|
|
63
|
+
});
|
|
64
|
+
response.on("end", () => {
|
|
65
|
+
try {
|
|
66
|
+
resolve(JSON.parse(body));
|
|
67
|
+
} catch (error) {
|
|
68
|
+
reject(error);
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
})
|
|
72
|
+
.on("error", reject);
|
|
73
|
+
|
|
74
|
+
request.setTimeout(defaultTimeoutMs, () => {
|
|
75
|
+
request.destroy(new Error("update check timed out"));
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function withTimeout(promise, timeoutMs) {
|
|
81
|
+
return new Promise((resolve, reject) => {
|
|
82
|
+
const timer = setTimeout(() => {
|
|
83
|
+
reject(new Error("update check timed out"));
|
|
84
|
+
}, timeoutMs);
|
|
85
|
+
|
|
86
|
+
Promise.resolve(promise).then(
|
|
87
|
+
(value) => {
|
|
88
|
+
clearTimeout(timer);
|
|
89
|
+
resolve(value);
|
|
90
|
+
},
|
|
91
|
+
(error) => {
|
|
92
|
+
clearTimeout(timer);
|
|
93
|
+
reject(error);
|
|
94
|
+
},
|
|
95
|
+
);
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function compareVersions(left, right) {
|
|
100
|
+
const leftParts = parseStableVersion(left);
|
|
101
|
+
const rightParts = parseStableVersion(right);
|
|
102
|
+
if (!leftParts || !rightParts) {
|
|
103
|
+
return 0;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
for (let index = 0; index < 3; index += 1) {
|
|
107
|
+
if (leftParts[index] > rightParts[index]) {
|
|
108
|
+
return 1;
|
|
109
|
+
}
|
|
110
|
+
if (leftParts[index] < rightParts[index]) {
|
|
111
|
+
return -1;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return 0;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function parseStableVersion(version) {
|
|
118
|
+
const match = /^(\d+)\.(\d+)\.(\d+)$/.exec(String(version));
|
|
119
|
+
if (!match) {
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
return match.slice(1).map((part) => Number.parseInt(part, 10));
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function isRedirect(statusCode) {
|
|
126
|
+
return [301, 302, 303, 307, 308].includes(statusCode);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
module.exports = {
|
|
130
|
+
checkForUpdate,
|
|
131
|
+
compareVersions,
|
|
132
|
+
};
|
|
Binary file
|