omnindicator 0.1.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/LICENSE +201 -0
- package/README.md +959 -0
- package/docs/npm-installer.md +91 -0
- package/npm/omnindicator/bin/omnindicator.js +11 -0
- package/npm/omnindicator/lib/cli.js +267 -0
- package/npm/omnindicator/lib/host.js +207 -0
- package/npm/omnindicator/lib/installer.js +178 -0
- package/npm/omnindicator/lib/models.js +114 -0
- package/package.json +40 -0
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const fs = require("node:fs");
|
|
4
|
+
const os = require("node:os");
|
|
5
|
+
const path = require("node:path");
|
|
6
|
+
const { spawnSync } = require("node:child_process");
|
|
7
|
+
const { getModel } = require("./models");
|
|
8
|
+
|
|
9
|
+
const REPOSITORY_URL = "https://github.com/robit-man/qwen-omni-adapters.git";
|
|
10
|
+
const RELEASE_REF = "npm-v0.1.0";
|
|
11
|
+
|
|
12
|
+
function dataRoot(platform = process.platform, env = process.env) {
|
|
13
|
+
if (env.OMNINDICATOR_HOME) return path.resolve(env.OMNINDICATOR_HOME);
|
|
14
|
+
if (platform === "win32") {
|
|
15
|
+
return path.join(env.LOCALAPPDATA || path.join(os.homedir(), "AppData", "Local"), "omnindicator");
|
|
16
|
+
}
|
|
17
|
+
if (platform === "darwin") {
|
|
18
|
+
return path.join(os.homedir(), "Library", "Application Support", "omnindicator");
|
|
19
|
+
}
|
|
20
|
+
return path.join(env.XDG_DATA_HOME || path.join(os.homedir(), ".local", "share"), "omnindicator");
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function run(command, args, options = {}) {
|
|
24
|
+
const result = spawnSync(command, args, {
|
|
25
|
+
cwd: options.cwd,
|
|
26
|
+
env: options.env || process.env,
|
|
27
|
+
encoding: "utf8",
|
|
28
|
+
stdio: options.inherit ? "inherit" : "pipe",
|
|
29
|
+
windowsHide: true,
|
|
30
|
+
});
|
|
31
|
+
if (result.error) throw result.error;
|
|
32
|
+
if (result.status !== 0) {
|
|
33
|
+
const detail = options.inherit ? "" : `: ${(result.stderr || result.stdout || "").trim()}`;
|
|
34
|
+
throw new Error(`${path.basename(command)} ${args.join(" ")} failed${detail}`);
|
|
35
|
+
}
|
|
36
|
+
return (result.stdout || "").trim();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function normalizeRemote(value) {
|
|
40
|
+
return value
|
|
41
|
+
.trim()
|
|
42
|
+
.replace(/^git@github\.com:/, "https://github.com/")
|
|
43
|
+
.replace(/^ssh:\/\/git@github\.com\//, "https://github.com/")
|
|
44
|
+
.replace(/\.git$/, "")
|
|
45
|
+
.replace(/\/$/, "")
|
|
46
|
+
.toLowerCase();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function validateCheckout(repoDir) {
|
|
50
|
+
const expected = ["AGENTS.md", "deploy.sh", "deploy-macos.sh", "deploy.ps1"];
|
|
51
|
+
const missing = expected.filter((name) => !fs.existsSync(path.join(repoDir, name)));
|
|
52
|
+
if (missing.length) throw new Error(`${repoDir} is not a complete qwen-omni-adapters checkout (missing ${missing.join(", ")}).`);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function validateOrigin(repoDir, git) {
|
|
56
|
+
const remote = run(git, ["remote", "get-url", "origin"], { cwd: repoDir });
|
|
57
|
+
if (normalizeRemote(remote) !== normalizeRemote(REPOSITORY_URL)) {
|
|
58
|
+
throw new Error(`Refusing to execute an unexpected checkout origin: ${remote}.`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function releaseTarget(repoDir, git, ref) {
|
|
63
|
+
run(git, ["fetch", "--force", "--depth=100", "origin", "main", `refs/tags/${ref}:refs/tags/${ref}`], { cwd: repoDir });
|
|
64
|
+
const target = run(git, ["rev-list", "-n", "1", ref], { cwd: repoDir });
|
|
65
|
+
const belongsToMain = spawnSync(git, ["merge-base", "--is-ancestor", target, "origin/main"], { cwd: repoDir }).status === 0;
|
|
66
|
+
if (!belongsToMain) throw new Error(`Release ${ref} is not reachable from origin/main; refusing the checkout.`);
|
|
67
|
+
return target;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function ensureRepository(options) {
|
|
71
|
+
const repoDir = path.resolve(options.repoDir);
|
|
72
|
+
const root = path.dirname(repoDir);
|
|
73
|
+
const statePath = path.join(root, "install-state.json");
|
|
74
|
+
const ref = options.ref || RELEASE_REF;
|
|
75
|
+
const git = options.git || "git";
|
|
76
|
+
|
|
77
|
+
if (fs.existsSync(repoDir)) {
|
|
78
|
+
validateCheckout(repoDir);
|
|
79
|
+
validateOrigin(repoDir, git);
|
|
80
|
+
if (options.externalCheckout) return { repoDir, created: false, updated: false, source: "existing" };
|
|
81
|
+
|
|
82
|
+
if (!fs.existsSync(statePath)) {
|
|
83
|
+
throw new Error(`${repoDir} already exists but is not marked as an omnindicator-managed checkout; pass --repo-dir explicitly to use it without changing Git state.`);
|
|
84
|
+
}
|
|
85
|
+
const dirty = run(git, ["status", "--porcelain", "--untracked-files=no"], { cwd: repoDir });
|
|
86
|
+
if (dirty) throw new Error("Tracked checkout changes are present; refusing to move the managed release checkout.");
|
|
87
|
+
const branch = run(git, ["branch", "--show-current"], { cwd: repoDir });
|
|
88
|
+
if (branch !== "main") throw new Error(`Managed checkout must remain on main for indicator updates (found ${branch || "detached HEAD"}).`);
|
|
89
|
+
const target = releaseTarget(repoDir, git, ref);
|
|
90
|
+
const head = run(git, ["rev-parse", "HEAD"], { cwd: repoDir });
|
|
91
|
+
let updated = false;
|
|
92
|
+
if (head !== target) {
|
|
93
|
+
const targetIsOlder = spawnSync(git, ["merge-base", "--is-ancestor", target, head], { cwd: repoDir }).status === 0;
|
|
94
|
+
if (!targetIsOlder) {
|
|
95
|
+
run(git, ["merge", "--ff-only", target], { cwd: repoDir });
|
|
96
|
+
updated = true;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return { repoDir, created: false, updated, source: ref };
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
fs.mkdirSync(root, { recursive: true, mode: 0o700 });
|
|
103
|
+
run(git, ["clone", "--branch", "main", "--single-branch", "--filter=blob:none", REPOSITORY_URL, repoDir], { inherit: true });
|
|
104
|
+
const target = releaseTarget(repoDir, git, ref);
|
|
105
|
+
run(git, ["checkout", "-B", "main", target], { cwd: repoDir });
|
|
106
|
+
run(git, ["branch", "--set-upstream-to=origin/main", "main"], { cwd: repoDir });
|
|
107
|
+
validateCheckout(repoDir);
|
|
108
|
+
fs.writeFileSync(statePath, `${JSON.stringify({ repository: REPOSITORY_URL, ref, repoDir }, null, 2)}\n`, { mode: 0o600 });
|
|
109
|
+
return { repoDir, created: true, updated: false, source: ref };
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function buildDeployment(options) {
|
|
113
|
+
const model = getModel(options.profile);
|
|
114
|
+
if (!model) throw new Error(`Unknown model profile: ${options.profile}`);
|
|
115
|
+
const environment = {
|
|
116
|
+
...process.env,
|
|
117
|
+
OMNI_PROFILE: model.id,
|
|
118
|
+
OMNI_MODEL: model.tag,
|
|
119
|
+
OMNI_LANGUAGE_MODEL: model.tag,
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
if (options.platform === "linux") {
|
|
123
|
+
return {
|
|
124
|
+
command: "bash",
|
|
125
|
+
args: [
|
|
126
|
+
path.join(options.repoDir, "deploy.sh"),
|
|
127
|
+
"--profile", model.id,
|
|
128
|
+
"--action", options.existing ? "deploy" : "install",
|
|
129
|
+
options.coreOnly ? "--no-harness" : "--with-harness",
|
|
130
|
+
"--yes",
|
|
131
|
+
],
|
|
132
|
+
cwd: options.repoDir,
|
|
133
|
+
env: environment,
|
|
134
|
+
indicator: !options.coreOnly,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
if (options.platform === "darwin") {
|
|
138
|
+
return {
|
|
139
|
+
command: "bash",
|
|
140
|
+
args: [path.join(options.repoDir, "deploy-macos.sh")],
|
|
141
|
+
cwd: options.repoDir,
|
|
142
|
+
env: environment,
|
|
143
|
+
indicator: false,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
return {
|
|
147
|
+
command: "powershell",
|
|
148
|
+
args: ["-NoProfile", "-ExecutionPolicy", "Bypass", "-File", path.join(options.repoDir, "deploy.ps1")],
|
|
149
|
+
cwd: options.repoDir,
|
|
150
|
+
env: environment,
|
|
151
|
+
indicator: false,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function runDeployment(plan) {
|
|
156
|
+
run(plan.command, plan.args, { cwd: plan.cwd, env: plan.env, inherit: true });
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function runStatus(repoDir, platform) {
|
|
160
|
+
const executable = platform === "win32"
|
|
161
|
+
? path.join(repoDir, ".venv", "Scripts", "qwen-omni-daemon.exe")
|
|
162
|
+
: path.join(repoDir, ".venv", "bin", "qwen-omni-daemon");
|
|
163
|
+
if (!fs.existsSync(executable)) throw new Error(`No deployed runtime found at ${repoDir}.`);
|
|
164
|
+
run(executable, ["status"], { cwd: repoDir, inherit: true });
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
module.exports = {
|
|
168
|
+
RELEASE_REF,
|
|
169
|
+
REPOSITORY_URL,
|
|
170
|
+
buildDeployment,
|
|
171
|
+
dataRoot,
|
|
172
|
+
ensureRepository,
|
|
173
|
+
normalizeRemote,
|
|
174
|
+
runDeployment,
|
|
175
|
+
runStatus,
|
|
176
|
+
validateCheckout,
|
|
177
|
+
validateOrigin,
|
|
178
|
+
};
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const MODELS = Object.freeze([
|
|
4
|
+
Object.freeze({
|
|
5
|
+
id: "ornith15",
|
|
6
|
+
title: "Standard Ornith 1.5 9B audio bridge",
|
|
7
|
+
tag: "robit/ornith-1.5-omni-audio-bridge:q4km",
|
|
8
|
+
artifactGiB: 8.15,
|
|
9
|
+
diskFloorGiB: 28,
|
|
10
|
+
hostMemoryFloorGiB: 16,
|
|
11
|
+
discreteFloorGiB: 16,
|
|
12
|
+
discreteRecommendedGiB: 20,
|
|
13
|
+
tegraFloorGiB: 29,
|
|
14
|
+
tegraRecommendedGiB: 29,
|
|
15
|
+
macFloorGiB: 29,
|
|
16
|
+
macRecommendedGiB: 32,
|
|
17
|
+
}),
|
|
18
|
+
Object.freeze({
|
|
19
|
+
id: "qwen38",
|
|
20
|
+
title: "Qwen3.8 27B E03 Obliterated audio bridge",
|
|
21
|
+
tag: "robit/qwen3.8-27b-e03-obliterated-omni-audio-bridge:q4km",
|
|
22
|
+
artifactGiB: 18.33,
|
|
23
|
+
diskFloorGiB: 42,
|
|
24
|
+
hostMemoryFloorGiB: 32,
|
|
25
|
+
discreteFloorGiB: 24,
|
|
26
|
+
discreteRecommendedGiB: 32,
|
|
27
|
+
tegraFloorGiB: 29,
|
|
28
|
+
tegraRecommendedGiB: 48,
|
|
29
|
+
macFloorGiB: 32,
|
|
30
|
+
macRecommendedGiB: 48,
|
|
31
|
+
}),
|
|
32
|
+
]);
|
|
33
|
+
|
|
34
|
+
function round1(value) {
|
|
35
|
+
return Math.round(value * 10) / 10;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function profileFit(model, host) {
|
|
39
|
+
const diskWarning = Number.isFinite(host.diskFreeGiB) && host.diskFreeGiB < model.diskFloorGiB
|
|
40
|
+
? `Only ${round1(host.diskFreeGiB)} GiB is free on the install filesystem; allow at least ${model.diskFloorGiB} GiB.`
|
|
41
|
+
: null;
|
|
42
|
+
|
|
43
|
+
let available;
|
|
44
|
+
let floor;
|
|
45
|
+
let recommended;
|
|
46
|
+
let resource;
|
|
47
|
+
|
|
48
|
+
if (!host.tegra && host.platform !== "darwin" && host.totalMemoryGiB < model.hostMemoryFloorGiB) {
|
|
49
|
+
return {
|
|
50
|
+
...model,
|
|
51
|
+
state: "unsupported",
|
|
52
|
+
reason: `${round1(host.totalMemoryGiB)} GiB system memory detected; this profile requires at least ${model.hostMemoryFloorGiB} GiB of host memory in addition to GPU capacity.`,
|
|
53
|
+
diskWarning,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (host.tegra) {
|
|
58
|
+
available = host.totalMemoryGiB;
|
|
59
|
+
floor = model.tegraFloorGiB;
|
|
60
|
+
recommended = model.tegraRecommendedGiB;
|
|
61
|
+
resource = "unified memory";
|
|
62
|
+
} else if (host.platform === "darwin") {
|
|
63
|
+
available = host.totalMemoryGiB;
|
|
64
|
+
floor = model.macFloorGiB;
|
|
65
|
+
recommended = model.macRecommendedGiB;
|
|
66
|
+
resource = "unified memory";
|
|
67
|
+
} else {
|
|
68
|
+
available = host.maxGpuMemoryGiB;
|
|
69
|
+
floor = model.discreteFloorGiB;
|
|
70
|
+
recommended = model.discreteRecommendedGiB;
|
|
71
|
+
resource = "GPU memory";
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (!Number.isFinite(available) || available <= 0) {
|
|
75
|
+
return {
|
|
76
|
+
...model,
|
|
77
|
+
state: "unsupported",
|
|
78
|
+
reason: `No usable ${resource} capacity was detected.`,
|
|
79
|
+
diskWarning,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
if (available < floor) {
|
|
83
|
+
return {
|
|
84
|
+
...model,
|
|
85
|
+
state: "unsupported",
|
|
86
|
+
reason: `${round1(available)} GiB ${resource} detected; this profile requires at least a ${floor} GiB-class device.`,
|
|
87
|
+
diskWarning,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
if (available < recommended) {
|
|
91
|
+
return {
|
|
92
|
+
...model,
|
|
93
|
+
state: "conditional",
|
|
94
|
+
reason: `${round1(available)} GiB ${resource} passes the coarse floor but is below the ${recommended} GiB recommendation; the native live-admission gate remains authoritative.`,
|
|
95
|
+
diskWarning,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
return {
|
|
99
|
+
...model,
|
|
100
|
+
state: "supported",
|
|
101
|
+
reason: `${round1(available)} GiB ${resource} meets the ${recommended} GiB recommendation; live load and residency checks still run before service cutover.`,
|
|
102
|
+
diskWarning,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function evaluateProfiles(host) {
|
|
107
|
+
return MODELS.map((model) => profileFit(model, host));
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function getModel(id) {
|
|
111
|
+
return MODELS.find((model) => model.id === id) || null;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
module.exports = { MODELS, evaluateProfiles, getModel, profileFit };
|
package/package.json
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "omnindicator",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Guarded host doctor and installer for the Qwen Omni desktop indicator runtime",
|
|
5
|
+
"license": "Apache-2.0",
|
|
6
|
+
"author": "Robit",
|
|
7
|
+
"homepage": "https://github.com/robit-man/qwen-omni-adapters#readme",
|
|
8
|
+
"bugs": {
|
|
9
|
+
"url": "https://github.com/robit-man/qwen-omni-adapters/issues"
|
|
10
|
+
},
|
|
11
|
+
"repository": {
|
|
12
|
+
"type": "git",
|
|
13
|
+
"url": "git+https://github.com/robit-man/qwen-omni-adapters.git"
|
|
14
|
+
},
|
|
15
|
+
"bin": {
|
|
16
|
+
"omnindicator": "npm/omnindicator/bin/omnindicator.js"
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"npm/omnindicator/bin",
|
|
20
|
+
"npm/omnindicator/lib",
|
|
21
|
+
"docs/npm-installer.md"
|
|
22
|
+
],
|
|
23
|
+
"scripts": {
|
|
24
|
+
"test": "node --test npm/omnindicator/test/*.test.js"
|
|
25
|
+
},
|
|
26
|
+
"engines": {
|
|
27
|
+
"node": ">=18.18"
|
|
28
|
+
},
|
|
29
|
+
"keywords": [
|
|
30
|
+
"qwen",
|
|
31
|
+
"omni",
|
|
32
|
+
"jetson",
|
|
33
|
+
"ollama",
|
|
34
|
+
"voice-assistant",
|
|
35
|
+
"appindicator"
|
|
36
|
+
],
|
|
37
|
+
"publishConfig": {
|
|
38
|
+
"access": "public"
|
|
39
|
+
}
|
|
40
|
+
}
|