@hasna/economy 0.4.0 → 0.5.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/CHANGELOG.md +24 -0
- package/README.md +12 -9
- package/dist/chunks/agent-registry-store-tpnhx3wj.js +39 -0
- package/dist/chunks/billing-bkpxy4kv.js +13 -0
- package/dist/chunks/config-j9mv3ehk.js +15 -0
- package/dist/chunks/index-2hwzmfgv.js +3407 -0
- package/dist/chunks/index-axh32d30.js +1313 -0
- package/dist/chunks/index-emvs5ph2.js +2085 -0
- package/dist/chunks/index-ez9thsr7.js +3440 -0
- package/dist/chunks/index-hesxq90g.js +62 -0
- package/dist/chunks/index-k540hfww.js +258 -0
- package/dist/chunks/index-kgy9b6bf.js +451 -0
- package/dist/chunks/index-kn5572kd.js +124 -0
- package/dist/chunks/index-p35cyhc4.js +4 -0
- package/dist/chunks/index-w3bvv0ns.js +26 -0
- package/dist/chunks/index-zpvea08j.js +3372 -0
- package/dist/chunks/menubar-6xtkt17h.js +296 -0
- package/dist/chunks/open-projects-6z4pq6mx.js +39 -0
- package/dist/chunks/pricing-f80p81gr.js +29 -0
- package/dist/chunks/pricing-vf2pycmh.js +30 -0
- package/dist/chunks/pricing-y21mcjsa.js +30 -0
- package/dist/chunks/serve-0k69e4h9.js +4655 -0
- package/dist/chunks/sqlite-store-501f32ts.js +258 -0
- package/dist/chunks/sqlite-store-fq4natnf.js +256 -0
- package/dist/chunks/sqlite-store-st99garx.js +258 -0
- package/dist/chunks/third-party-sqlite-5e85fxa2.js +11 -0
- package/dist/chunks/tui-7f8f951w.js +22 -0
- package/dist/chunks/watch-mm1j39vy.js +196 -0
- package/dist/chunks/webhooks-k4fh2t9c.js +109 -0
- package/dist/cli/commands/brief.d.ts.map +1 -1
- package/dist/cli/commands/extras.d.ts.map +1 -1
- package/dist/cli/commands/journal.d.ts +3 -0
- package/dist/cli/commands/journal.d.ts.map +1 -0
- package/dist/cli/commands/menubar.d.ts +97 -4
- package/dist/cli/commands/menubar.d.ts.map +1 -1
- package/dist/cli/commands/tui.d.ts +1 -1
- package/dist/cli/commands/tui.d.ts.map +1 -1
- package/dist/cli/commands/watch.d.ts.map +1 -1
- package/dist/cli/index.js +1358 -10114
- package/dist/db/cloud.d.ts +7 -9
- package/dist/db/cloud.d.ts.map +1 -1
- package/dist/db/database.d.ts +14 -12
- package/dist/db/database.d.ts.map +1 -1
- package/dist/db/ingest-concurrency-worker.d.ts +2 -0
- package/dist/db/ingest-concurrency-worker.d.ts.map +1 -0
- package/dist/db/ingest-errors.d.ts +9 -0
- package/dist/db/ingest-errors.d.ts.map +1 -0
- package/dist/db/ingest-schema.d.ts +3 -0
- package/dist/db/ingest-schema.d.ts.map +1 -0
- package/dist/db/ingest-specs.d.ts +8 -0
- package/dist/db/ingest-specs.d.ts.map +1 -0
- package/dist/db/ingest-test-contract.d.ts +3 -0
- package/dist/db/ingest-test-contract.d.ts.map +1 -0
- package/dist/db/ingest-validation.d.ts +12 -0
- package/dist/db/ingest-validation.d.ts.map +1 -0
- package/dist/db/ingest.d.ts +27 -0
- package/dist/db/ingest.d.ts.map +1 -0
- package/dist/db/legacy-schema.d.ts +7 -0
- package/dist/db/legacy-schema.d.ts.map +1 -0
- package/dist/db/observation-contract.d.ts +3 -0
- package/dist/db/observation-contract.d.ts.map +1 -0
- package/dist/db/observation-provenance.d.ts +23 -0
- package/dist/db/observation-provenance.d.ts.map +1 -0
- package/dist/db/observation-schema.d.ts +24 -0
- package/dist/db/observation-schema.d.ts.map +1 -0
- package/dist/db/pg-connection.d.ts +10 -0
- package/dist/db/pg-connection.d.ts.map +1 -0
- package/dist/db/pg-migrate.d.ts +4 -9
- package/dist/db/pg-migrate.d.ts.map +1 -1
- package/dist/db/pg-migrations.d.ts.map +1 -1
- package/dist/db/pg-protocol.d.ts +28 -0
- package/dist/db/pg-protocol.d.ts.map +1 -0
- package/dist/db/sqlite-store.d.ts +4 -0
- package/dist/db/sqlite-store.d.ts.map +1 -0
- package/dist/db/sync-pg.d.ts +17 -21
- package/dist/db/sync-pg.d.ts.map +1 -1
- package/dist/db/third-party-sqlite.d.ts +10 -0
- package/dist/db/third-party-sqlite.d.ts.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1161 -2464
- package/dist/ingest/claude-quota.d.ts.map +1 -1
- package/dist/ingest/claude.d.ts.map +1 -1
- package/dist/ingest/codex-quota.d.ts.map +1 -1
- package/dist/ingest/codex.d.ts +25 -0
- package/dist/ingest/codex.d.ts.map +1 -1
- package/dist/ingest/collection-outcome.d.ts +17 -0
- package/dist/ingest/collection-outcome.d.ts.map +1 -0
- package/dist/ingest/cursor.d.ts.map +1 -1
- package/dist/ingest/gemini.d.ts.map +1 -1
- package/dist/ingest/hermes.d.ts +1 -1
- package/dist/ingest/hermes.d.ts.map +1 -1
- package/dist/ingest/loops.d.ts.map +1 -1
- package/dist/ingest/opencode.d.ts +1 -1
- package/dist/ingest/opencode.d.ts.map +1 -1
- package/dist/ingest/pi.d.ts +1 -1
- package/dist/ingest/pi.d.ts.map +1 -1
- package/dist/ingest/quota-observation.d.ts +16 -0
- package/dist/ingest/quota-observation.d.ts.map +1 -0
- package/dist/ingest/source-time.d.ts +4 -0
- package/dist/ingest/source-time.d.ts.map +1 -0
- package/dist/lib/accounts-store.d.ts +39 -13
- package/dist/lib/accounts-store.d.ts.map +1 -1
- package/dist/lib/analytics.d.ts.map +1 -1
- package/dist/lib/api-display-url.d.ts +1 -1
- package/dist/lib/autosync-gate.d.ts +12 -5
- package/dist/lib/autosync-gate.d.ts.map +1 -1
- package/dist/lib/billing-diff.d.ts +18 -40
- package/dist/lib/billing-diff.d.ts.map +1 -1
- package/dist/lib/brief.d.ts +2 -0
- package/dist/lib/brief.d.ts.map +1 -1
- package/dist/lib/budget-decimal.d.ts +14 -0
- package/dist/lib/budget-decimal.d.ts.map +1 -0
- package/dist/lib/cloud-ingest.d.ts +15 -0
- package/dist/lib/cloud-ingest.d.ts.map +1 -1
- package/dist/lib/cloud-storage.d.ts.map +1 -1
- package/dist/lib/ingest-batches.d.ts +21 -0
- package/dist/lib/ingest-batches.d.ts.map +1 -0
- package/dist/lib/ingest-journal-client.d.ts +37 -0
- package/dist/lib/ingest-journal-client.d.ts.map +1 -0
- package/dist/lib/ingest-journal-lock.d.ts +5 -0
- package/dist/lib/ingest-journal-lock.d.ts.map +1 -0
- package/dist/lib/ingest-journal-maintenance.d.ts +92 -0
- package/dist/lib/ingest-journal-maintenance.d.ts.map +1 -0
- package/dist/lib/ingest-journal-operation.d.ts +55 -0
- package/dist/lib/ingest-journal-operation.d.ts.map +1 -0
- package/dist/lib/ingest-journal.d.ts +73 -0
- package/dist/lib/ingest-journal.d.ts.map +1 -0
- package/dist/lib/money-display.d.ts +13 -0
- package/dist/lib/money-display.d.ts.map +1 -0
- package/dist/lib/periods.d.ts +12 -0
- package/dist/lib/periods.d.ts.map +1 -1
- package/dist/lib/pricing.d.ts +14 -0
- package/dist/lib/pricing.d.ts.map +1 -1
- package/dist/lib/savings.d.ts +64 -7
- package/dist/lib/savings.d.ts.map +1 -1
- package/dist/lib/store/index.d.ts +25 -21
- package/dist/lib/store/index.d.ts.map +1 -1
- package/dist/lib/summary-contract.d.ts +32 -0
- package/dist/lib/summary-contract.d.ts.map +1 -0
- package/dist/lib/summary-test-contract.d.ts +3 -0
- package/dist/lib/summary-test-contract.d.ts.map +1 -0
- package/dist/lib/sync-all.d.ts +4 -0
- package/dist/lib/sync-all.d.ts.map +1 -1
- package/dist/lib/sync-maintenance.d.ts.map +1 -1
- package/dist/lib/sync-report.d.ts +4 -0
- package/dist/lib/sync-report.d.ts.map +1 -0
- package/dist/lib/test-keychain-fixture.d.ts +9 -0
- package/dist/lib/test-keychain-fixture.d.ts.map +1 -0
- package/dist/mcp/agent-registry-store.d.ts +30 -0
- package/dist/mcp/agent-registry-store.d.ts.map +1 -0
- package/dist/mcp/agent-registry.d.ts +9 -16
- package/dist/mcp/agent-registry.d.ts.map +1 -1
- package/dist/mcp/index.js +2281 -3457
- package/dist/mcp/ingest-journal-tool.d.ts +2 -0
- package/dist/mcp/ingest-journal-tool.d.ts.map +1 -0
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/openapi.d.ts.map +1 -1
- package/dist/otel/index.js +1950 -288
- package/dist/server/human-auth.d.ts +20 -0
- package/dist/server/human-auth.d.ts.map +1 -0
- package/dist/server/index.js +10401 -6695
- package/dist/server/legacy-inventory.d.ts +52 -0
- package/dist/server/legacy-inventory.d.ts.map +1 -0
- package/dist/server/legacy-operations.d.ts +64 -0
- package/dist/server/legacy-operations.d.ts.map +1 -0
- package/dist/server/legacy-plan.d.ts +55 -0
- package/dist/server/legacy-plan.d.ts.map +1 -0
- package/dist/server/legacy-preservation.d.ts +58 -0
- package/dist/server/legacy-preservation.d.ts.map +1 -0
- package/dist/server/pg-sync-worker.js +195 -21
- package/dist/server/serve.d.ts +20 -16
- package/dist/server/serve.d.ts.map +1 -1
- package/dist/server/service-admission.d.ts +10 -0
- package/dist/server/service-admission.d.ts.map +1 -0
- package/dist/server/service-dispatcher.d.ts +45 -0
- package/dist/server/service-dispatcher.d.ts.map +1 -0
- package/dist/server/service-operations.d.ts +5 -0
- package/dist/server/service-operations.d.ts.map +1 -0
- package/dist/server/service-protocol.d.ts +50 -0
- package/dist/server/service-protocol.d.ts.map +1 -0
- package/dist/server/service-worker.d.ts +2 -0
- package/dist/server/service-worker.d.ts.map +1 -0
- package/dist/server/service-worker.js +8029 -0
- package/dist/types/index.d.ts +66 -0
- package/dist/types/index.d.ts.map +1 -1
- package/docs/README.md +1 -1
- package/docs/cli.md +1 -1
- package/docs/configuration.md +5 -5
- package/docs/cost-reporting.md +49 -0
- package/docs/historical-data-reconciliation.md +203 -0
- package/docs/ingest-journal.md +108 -0
- package/docs/ingestion.md +60 -4
- package/docs/mcp.md +2 -2
- package/docs/native-budget-save.md +84 -0
- package/docs/native-human-auth.md +102 -0
- package/docs/rest-api.md +2 -2
- package/package.json +15 -12
- package/dist/lib/test-hermetic-accounts.d.ts +0 -7
- package/dist/lib/test-hermetic-accounts.d.ts.map +0 -1
- package/dist/lib/test-hermetic-fleet-env.d.ts +0 -4
- package/dist/lib/test-hermetic-fleet-env.d.ts.map +0 -1
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
import"./index-p35cyhc4.js";
|
|
3
|
+
|
|
4
|
+
// src/cli/commands/menubar.ts
|
|
5
|
+
import { createHash } from "crypto";
|
|
6
|
+
import { execFileSync } from "child_process";
|
|
7
|
+
import { closeSync, constants, fstatSync, lstatSync, mkdtempSync, openSync, readSync, readdirSync, realpathSync } from "fs";
|
|
8
|
+
import { arch, platform } from "os";
|
|
9
|
+
import { isAbsolute, join, resolve, dirname } from "path";
|
|
10
|
+
var APP_PATH = "/Applications/HasnaEconomy.app";
|
|
11
|
+
var BUNDLE_ID = "com.hasna.economy-bar";
|
|
12
|
+
var EXECUTABLE = "HasnaEconomy";
|
|
13
|
+
var TEAM_ID = "HKZ326A8Y3";
|
|
14
|
+
var TREE_FORMAT = "hasna-economy-app-tree-v1";
|
|
15
|
+
var MAX_ENTRIES = 1e4;
|
|
16
|
+
var MAX_BYTES = 1073741824;
|
|
17
|
+
function refuse(code, detail) {
|
|
18
|
+
throw new Error(`${code}: ${detail}`);
|
|
19
|
+
}
|
|
20
|
+
function occupied(path) {
|
|
21
|
+
try {
|
|
22
|
+
lstatSync(path);
|
|
23
|
+
return true;
|
|
24
|
+
} catch (error) {
|
|
25
|
+
if (error.code === "ENOENT")
|
|
26
|
+
return false;
|
|
27
|
+
return refuse("PATH_UNREADABLE", "The selected path cannot be inspected.");
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
function fingerprintMacApp(path) {
|
|
31
|
+
if (!isAbsolute(path) || resolve(path) !== path)
|
|
32
|
+
refuse("APP_PATH", "--app must be an absolute normalized path.");
|
|
33
|
+
const root = lstatSync(path);
|
|
34
|
+
if (!root.isDirectory() || root.isSymbolicLink())
|
|
35
|
+
refuse("APP_TREE", "The app must be a real directory.");
|
|
36
|
+
const hash = createHash("sha256").update(`${TREE_FORMAT}
|
|
37
|
+
`);
|
|
38
|
+
let entries = 0, bytes = 0;
|
|
39
|
+
function walk(relative) {
|
|
40
|
+
const file = relative ? join(path, relative) : path, stat = lstatSync(file);
|
|
41
|
+
if (++entries > MAX_ENTRIES)
|
|
42
|
+
refuse("APP_BOUND", "App contains too many entries.");
|
|
43
|
+
if (stat.isSymbolicLink() || !stat.isDirectory() && !stat.isFile() || stat.isFile() && stat.nlink !== 1)
|
|
44
|
+
refuse("APP_TREE", "App entries must be regular files or directories; links and special files are refused.");
|
|
45
|
+
if ((stat.mode & 3602) !== 0)
|
|
46
|
+
refuse("APP_MODE", "App entries must not have special permission bits or be writable by other users.");
|
|
47
|
+
if (stat.isDirectory() && !stat.isSymbolicLink()) {
|
|
48
|
+
hash.update(JSON.stringify([relative, "directory", stat.mode & 511]) + `
|
|
49
|
+
`);
|
|
50
|
+
for (const name of readdirSync(file).sort())
|
|
51
|
+
walk(relative ? `${relative}/${name}` : name);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
if (stat.size > MAX_BYTES || bytes + stat.size > MAX_BYTES)
|
|
55
|
+
refuse("APP_BOUND", "App exceeds the one GiB verification bound.");
|
|
56
|
+
const fd = openSync(file, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
|
|
57
|
+
try {
|
|
58
|
+
const before = fstatSync(fd);
|
|
59
|
+
if (!before.isFile() || before.dev !== stat.dev || before.ino !== stat.ino || before.size !== stat.size)
|
|
60
|
+
refuse("APP_CHANGED", "App changed while being read.");
|
|
61
|
+
const fileHash = createHash("sha256"), buffer = Buffer.alloc(65536);
|
|
62
|
+
let read = 0;
|
|
63
|
+
while (read < stat.size) {
|
|
64
|
+
const count = readSync(fd, buffer, 0, Math.min(buffer.length, stat.size - read), null);
|
|
65
|
+
if (count === 0)
|
|
66
|
+
refuse("APP_CHANGED", "App changed while being read.");
|
|
67
|
+
fileHash.update(buffer.subarray(0, count));
|
|
68
|
+
read += count;
|
|
69
|
+
}
|
|
70
|
+
const after = fstatSync(fd);
|
|
71
|
+
if (after.size !== before.size || after.mtimeMs !== before.mtimeMs || after.ctimeMs !== before.ctimeMs || after.mode !== before.mode || after.nlink !== 1)
|
|
72
|
+
refuse("APP_CHANGED", "App changed while being read.");
|
|
73
|
+
bytes += read;
|
|
74
|
+
hash.update(JSON.stringify([relative, "file", stat.mode & 511, read, fileHash.digest("hex")]) + `
|
|
75
|
+
`);
|
|
76
|
+
} finally {
|
|
77
|
+
closeSync(fd);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
walk("");
|
|
81
|
+
return { sha256: hash.digest("hex"), entries, bytes, format: TREE_FORMAT };
|
|
82
|
+
}
|
|
83
|
+
function copyMacAppIntoDirectory(source, directoryFd) {
|
|
84
|
+
if (!process.versions.bun)
|
|
85
|
+
refuse("BUN_REQUIRED", "Native app installation requires the Bun CLI runtime.");
|
|
86
|
+
const program = `import {dlopen,FFIType} from 'bun:ffi';
|
|
87
|
+
import {execFileSync} from 'node:child_process';
|
|
88
|
+
import {fstatSync} from 'node:fs';
|
|
89
|
+
if(!fstatSync(3).isDirectory())throw Error('reserved directory unavailable');
|
|
90
|
+
const native=dlopen('/usr/lib/libSystem.B.dylib',{fchdir:{args:[FFIType.i32],returns:FFIType.i32}});
|
|
91
|
+
if(native.symbols.fchdir(3)!==0)throw Error('reserved directory unavailable');
|
|
92
|
+
execFileSync('/bin/cp',['-pRn',process.argv.at(-1)+'/.','.'],{cwd:'.',stdio:['ignore','pipe','pipe'],timeout:60000,maxBuffer:1048576});`;
|
|
93
|
+
try {
|
|
94
|
+
execFileSync(process.execPath, ["--eval", program, "--", source], {
|
|
95
|
+
cwd: "/",
|
|
96
|
+
env: { PATH: "/usr/bin:/bin", HOME: "/var/empty", LANG: "C" },
|
|
97
|
+
stdio: ["ignore", "pipe", "pipe", directoryFd],
|
|
98
|
+
timeout: 65000,
|
|
99
|
+
maxBuffer: 1048576
|
|
100
|
+
});
|
|
101
|
+
} catch {
|
|
102
|
+
refuse("NATIVE_COPY_FAILED", "The reserved-directory copy failed. No fallback was attempted.");
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
function publishMacAppExclusively(source, destination) {
|
|
106
|
+
if (!process.versions.bun)
|
|
107
|
+
refuse("BUN_REQUIRED", "Native app installation requires the Bun CLI runtime.");
|
|
108
|
+
const program = `import {dlopen,FFIType,ptr} from 'bun:ffi';
|
|
109
|
+
const native=dlopen('/usr/lib/libSystem.B.dylib',{renamex_np:{args:[FFIType.ptr,FFIType.ptr,FFIType.u32],returns:FFIType.i32}});
|
|
110
|
+
const source=Buffer.from(process.argv.at(-2)+'\\0'),destination=Buffer.from(process.argv.at(-1)+'\\0');
|
|
111
|
+
if(native.symbols.renamex_np(ptr(source),ptr(destination),4)!==0)process.exit(1);`;
|
|
112
|
+
try {
|
|
113
|
+
execFileSync(process.execPath, ["--eval", program, "--", source, destination], {
|
|
114
|
+
cwd: "/",
|
|
115
|
+
env: { PATH: "/usr/bin:/bin", HOME: "/var/empty", LANG: "C" },
|
|
116
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
117
|
+
timeout: 1e4,
|
|
118
|
+
maxBuffer: 65536
|
|
119
|
+
});
|
|
120
|
+
} catch {
|
|
121
|
+
refuse("ATOMIC_PUBLISH_REFUSED", "The verified stage could not be published without replacement. Reconcile the retained stage and destination before retrying.");
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
var runtime = {
|
|
125
|
+
copy: copyMacAppIntoDirectory,
|
|
126
|
+
publish: publishMacAppExclusively,
|
|
127
|
+
platform: platform(),
|
|
128
|
+
architecture: arch() === "x64" ? "x86_64" : arch(),
|
|
129
|
+
appPath: APP_PATH,
|
|
130
|
+
run(command, args) {
|
|
131
|
+
try {
|
|
132
|
+
return execFileSync(command, args, { encoding: "utf8", timeout: command === "trash" ? 300000 : 60000, maxBuffer: 1048576, stdio: ["ignore", "pipe", "pipe"] }).trim();
|
|
133
|
+
} catch {
|
|
134
|
+
return refuse("NATIVE_COMMAND_FAILED", `The ${command === "trash" ? "Trash capture" : command.split("/").pop()} operation failed. No fallback was attempted.`);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
function createMenubarManager(host = runtime) {
|
|
139
|
+
function mac() {
|
|
140
|
+
if (host.platform !== "darwin")
|
|
141
|
+
refuse("MACOS_REQUIRED", "The native menu-bar app requires macOS.");
|
|
142
|
+
}
|
|
143
|
+
function plist(path, key) {
|
|
144
|
+
return host.run("/usr/bin/plutil", ["-extract", key, "raw", "-o", "-", join(path, "Contents/Info.plist")]);
|
|
145
|
+
}
|
|
146
|
+
function quarantineFlags(path) {
|
|
147
|
+
const names = host.run("/usr/bin/xattr", [path]).split(`
|
|
148
|
+
`);
|
|
149
|
+
if (!names.includes("com.apple.quarantine"))
|
|
150
|
+
return null;
|
|
151
|
+
const value = host.run("/usr/bin/xattr", ["-p", "com.apple.quarantine", path]);
|
|
152
|
+
const flags = /^([a-fA-F0-9]{1,4});/.exec(value);
|
|
153
|
+
if (!flags)
|
|
154
|
+
refuse("QUARANTINE_INVALID", "The app quarantine state could not be verified.");
|
|
155
|
+
return Number.parseInt(flags[1], 16);
|
|
156
|
+
}
|
|
157
|
+
function verify(path) {
|
|
158
|
+
mac();
|
|
159
|
+
const before = fingerprintMacApp(path);
|
|
160
|
+
if (plist(path, "CFBundleIdentifier") !== BUNDLE_ID || plist(path, "CFBundleExecutable") !== EXECUTABLE || plist(path, "CFBundleName") !== EXECUTABLE)
|
|
161
|
+
refuse("BUNDLE_IDENTITY", "Expected the HasnaEconomy release bundle.");
|
|
162
|
+
const version = plist(path, "CFBundleShortVersionString"), build = plist(path, "CFBundleVersion");
|
|
163
|
+
if (!/^\d+(?:\.\d+){1,2}$/.test(version) || !/^\d+(?:\.\d+){0,2}$/.test(build))
|
|
164
|
+
refuse("BUNDLE_VERSION", "Release version or build is invalid.");
|
|
165
|
+
if (!host.run("/usr/bin/lipo", ["-archs", join(path, "Contents/MacOS", EXECUTABLE)]).split(/\s+/).includes(host.architecture))
|
|
166
|
+
refuse("BUNDLE_ARCHITECTURE", "The app does not support this Mac architecture.");
|
|
167
|
+
host.run("/usr/bin/codesign", ["--verify", "--deep", "--strict", "-R", `anchor apple generic and certificate leaf[subject.OU] = "${TEAM_ID}" and identifier "${BUNDLE_ID}"`, path]);
|
|
168
|
+
host.run("/usr/sbin/spctl", ["--assess", "--type", "execute", path]);
|
|
169
|
+
const after = fingerprintMacApp(path);
|
|
170
|
+
if (before.sha256 !== after.sha256)
|
|
171
|
+
refuse("APP_CHANGED", "App changed during signature verification.");
|
|
172
|
+
return { path, bundle_id: BUNDLE_ID, version, build, team_id: TEAM_ID, architecture: host.architecture, root_mode: lstatSync(path).mode & 511, ...after };
|
|
173
|
+
}
|
|
174
|
+
function running() {
|
|
175
|
+
const result = host.run("/usr/bin/osascript", ["-l", "JavaScript", "-e", `Application(${JSON.stringify(host.appPath)}).running()`]);
|
|
176
|
+
if (result !== "true" && result !== "false")
|
|
177
|
+
refuse("RUNNING_STATE_UNKNOWN", "Could not determine whether HasnaEconomy is running.");
|
|
178
|
+
return result === "true";
|
|
179
|
+
}
|
|
180
|
+
return {
|
|
181
|
+
inspect(path) {
|
|
182
|
+
return { status: "verified", ...verify(path) };
|
|
183
|
+
},
|
|
184
|
+
install(options) {
|
|
185
|
+
mac();
|
|
186
|
+
if (!/^[a-f0-9]{64}$/.test(options.sha256 ?? "") || !/^\d+(?:\.\d+){1,2}$/.test(options.appVersion ?? ""))
|
|
187
|
+
refuse("RELEASE_SELECTION", "--app-version and a lowercase SHA-256 from the reviewed release are required.");
|
|
188
|
+
if (occupied(host.appPath))
|
|
189
|
+
refuse("DESTINATION_EXISTS", "HasnaEconomy.app already exists. Preserve it through Trash before an explicitly authorized replacement.");
|
|
190
|
+
const source = verify(options.app);
|
|
191
|
+
if (source.sha256 !== options.sha256 || source.version !== options.appVersion)
|
|
192
|
+
refuse("RELEASE_MISMATCH", "Selected app does not match the expected release version and content hash.");
|
|
193
|
+
const quarantine = quarantineFlags(options.app);
|
|
194
|
+
const parent = dirname(host.appPath), parentStat = lstatSync(parent);
|
|
195
|
+
if (!parentStat.isDirectory() || parentStat.isSymbolicLink() || realpathSync(parent) !== parent || (parentStat.mode & 2) !== 0)
|
|
196
|
+
refuse("DESTINATION_UNSAFE", "The installation parent must be a real, non-world-writable directory.");
|
|
197
|
+
const stage = mkdtempSync(join(parent, ".HasnaEconomy-install-"));
|
|
198
|
+
const reserved = lstatSync(stage);
|
|
199
|
+
let directoryFd, published = false;
|
|
200
|
+
try {
|
|
201
|
+
directoryFd = openSync(stage, constants.O_RDONLY | constants.O_DIRECTORY | constants.O_NOFOLLOW);
|
|
202
|
+
const opened = fstatSync(directoryFd);
|
|
203
|
+
if (!opened.isDirectory() || opened.dev !== reserved.dev || opened.ino !== reserved.ino)
|
|
204
|
+
refuse("STAGE_CHANGED", "The private stage changed before copying.");
|
|
205
|
+
host.copy(options.app, directoryFd);
|
|
206
|
+
const copied = lstatSync(stage);
|
|
207
|
+
if (!copied.isDirectory() || copied.isSymbolicLink() || copied.dev !== reserved.dev || copied.ino !== reserved.ino)
|
|
208
|
+
refuse("STAGE_CHANGED", "The private stage changed during copying.");
|
|
209
|
+
const copiedQuarantine = quarantineFlags(stage);
|
|
210
|
+
if (quarantine !== null && (copiedQuarantine === null || (copiedQuarantine & quarantine) !== quarantine))
|
|
211
|
+
refuse("QUARANTINE_LOST", "The copied app lost quarantine protection.");
|
|
212
|
+
const prepared = verify(stage);
|
|
213
|
+
if (prepared.sha256 !== source.sha256 || fingerprintMacApp(options.app).sha256 !== source.sha256)
|
|
214
|
+
refuse("COPY_MISMATCH", "The source or staged bundle differs from the selected release.");
|
|
215
|
+
host.publish(stage, host.appPath);
|
|
216
|
+
published = true;
|
|
217
|
+
const installed = verify(host.appPath);
|
|
218
|
+
if (installed.sha256 !== source.sha256)
|
|
219
|
+
refuse("INSTALL_CHANGED", "The published bundle changed before final readback.");
|
|
220
|
+
return { status: "installed", launch_requested: false, ...installed };
|
|
221
|
+
} catch (error) {
|
|
222
|
+
const code = error instanceof Error ? error.message.split(":", 1)[0] : "UNKNOWN";
|
|
223
|
+
return refuse("INSTALL_INCOMPLETE", `Installation requires reconciliation (${code}). ${published ? "The published destination requires inspection." : `Inspect the retained staging bundle at ${stage}.`} Source is retained; nothing was removed or launched.`);
|
|
224
|
+
} finally {
|
|
225
|
+
if (directoryFd !== undefined)
|
|
226
|
+
closeSync(directoryFd);
|
|
227
|
+
}
|
|
228
|
+
},
|
|
229
|
+
start() {
|
|
230
|
+
const info = verify(host.appPath);
|
|
231
|
+
host.run("/usr/bin/open", [host.appPath]);
|
|
232
|
+
return { status: "launch_requested", ...info };
|
|
233
|
+
},
|
|
234
|
+
stop() {
|
|
235
|
+
mac();
|
|
236
|
+
if (!occupied(host.appPath))
|
|
237
|
+
return { status: "not_installed", path: host.appPath };
|
|
238
|
+
if (!running())
|
|
239
|
+
return { status: "not_running", path: host.appPath };
|
|
240
|
+
host.run("/usr/bin/osascript", ["-l", "JavaScript", "-e", `Application(${JSON.stringify(host.appPath)}).quit()`]);
|
|
241
|
+
return { status: "quit_requested", path: host.appPath };
|
|
242
|
+
},
|
|
243
|
+
uninstall() {
|
|
244
|
+
mac();
|
|
245
|
+
if (!occupied(host.appPath))
|
|
246
|
+
return { status: "not_installed", path: host.appPath };
|
|
247
|
+
if (running())
|
|
248
|
+
refuse("APP_RUNNING", "Quit HasnaEconomy explicitly before removing it. No process was signaled.");
|
|
249
|
+
const before = fingerprintMacApp(host.appPath);
|
|
250
|
+
const raw = host.run("trash", ["put", host.appPath]);
|
|
251
|
+
if (occupied(host.appPath))
|
|
252
|
+
refuse("TRASH_INCOMPLETE", "Trash did not remove the app. Source is retained; check trash pending before retrying.");
|
|
253
|
+
let entry;
|
|
254
|
+
try {
|
|
255
|
+
const parsed = JSON.parse(raw);
|
|
256
|
+
if (Array.isArray(parsed) && parsed.length === 1 && parsed[0] && typeof parsed[0] === "object") {
|
|
257
|
+
const id = parsed[0].id;
|
|
258
|
+
if (typeof id === "string" && /^[a-zA-Z0-9_-]{1,200}$/.test(id) && !("error" in parsed[0]))
|
|
259
|
+
entry = id;
|
|
260
|
+
}
|
|
261
|
+
} catch {}
|
|
262
|
+
if (!entry)
|
|
263
|
+
refuse("TRASH_RECEIPT_UNKNOWN", "The app path is gone but Trash returned no single valid recovery entry. Reconcile trash pending and the selected capture before any retry.");
|
|
264
|
+
return { status: "trashed", path: host.appPath, entry_id: entry, sha256: before.sha256 };
|
|
265
|
+
}
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
function output(value) {
|
|
269
|
+
console.log(JSON.stringify(value));
|
|
270
|
+
}
|
|
271
|
+
function menubarInspect(options) {
|
|
272
|
+
output(createMenubarManager().inspect(options.app));
|
|
273
|
+
}
|
|
274
|
+
async function menubarInstall(options) {
|
|
275
|
+
output(createMenubarManager().install(options));
|
|
276
|
+
}
|
|
277
|
+
function menubarStart() {
|
|
278
|
+
output(createMenubarManager().start());
|
|
279
|
+
}
|
|
280
|
+
function menubarStop() {
|
|
281
|
+
output(createMenubarManager().stop());
|
|
282
|
+
}
|
|
283
|
+
function menubarUninstall() {
|
|
284
|
+
output(createMenubarManager().uninstall());
|
|
285
|
+
}
|
|
286
|
+
export {
|
|
287
|
+
publishMacAppExclusively,
|
|
288
|
+
menubarUninstall,
|
|
289
|
+
menubarStop,
|
|
290
|
+
menubarStart,
|
|
291
|
+
menubarInstall,
|
|
292
|
+
menubarInspect,
|
|
293
|
+
fingerprintMacApp,
|
|
294
|
+
createMenubarManager,
|
|
295
|
+
copyMacAppIntoDirectory
|
|
296
|
+
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
import {
|
|
3
|
+
upsertProject
|
|
4
|
+
} from "./index-ez9thsr7.js";
|
|
5
|
+
import"./index-p35cyhc4.js";
|
|
6
|
+
|
|
7
|
+
// src/lib/open-projects.ts
|
|
8
|
+
var resolveProjectPath = (project) => project.primary_path || project.path || "";
|
|
9
|
+
var PROJECTS_MODULE = "@hasna/projects";
|
|
10
|
+
async function syncOpenProjectsRegistry(db, listActiveProjects) {
|
|
11
|
+
let listProjects = listActiveProjects;
|
|
12
|
+
if (!listProjects) {
|
|
13
|
+
const projectsApi = await import(PROJECTS_MODULE);
|
|
14
|
+
listProjects = projectsApi.listProjects;
|
|
15
|
+
}
|
|
16
|
+
const projects = listProjects({ status: "active", limit: 5000 });
|
|
17
|
+
let imported = 0;
|
|
18
|
+
let skipped = 0;
|
|
19
|
+
for (const project of projects) {
|
|
20
|
+
const path = resolveProjectPath(project);
|
|
21
|
+
if (!path) {
|
|
22
|
+
skipped++;
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
upsertProject(db, {
|
|
26
|
+
id: project.id,
|
|
27
|
+
path,
|
|
28
|
+
name: project.name,
|
|
29
|
+
description: project.description,
|
|
30
|
+
tags: project.tags ?? [],
|
|
31
|
+
created_at: project.created_at
|
|
32
|
+
});
|
|
33
|
+
imported++;
|
|
34
|
+
}
|
|
35
|
+
return { imported, skipped };
|
|
36
|
+
}
|
|
37
|
+
export {
|
|
38
|
+
syncOpenProjectsRegistry
|
|
39
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
import {
|
|
3
|
+
DEFAULT_PRICING,
|
|
4
|
+
UnknownModelPricingError,
|
|
5
|
+
assessCostFromDb,
|
|
6
|
+
assessCostFromRows,
|
|
7
|
+
computeCost,
|
|
8
|
+
computeCostFromDb,
|
|
9
|
+
ensurePricingSeeded,
|
|
10
|
+
estimateCostFromRows,
|
|
11
|
+
getPricing,
|
|
12
|
+
getPricingFromDb,
|
|
13
|
+
getPricingFromRows,
|
|
14
|
+
normalizeModelName
|
|
15
|
+
} from "./index-zpvea08j.js";
|
|
16
|
+
export {
|
|
17
|
+
normalizeModelName,
|
|
18
|
+
getPricingFromRows,
|
|
19
|
+
getPricingFromDb,
|
|
20
|
+
getPricing,
|
|
21
|
+
estimateCostFromRows,
|
|
22
|
+
ensurePricingSeeded,
|
|
23
|
+
computeCostFromDb,
|
|
24
|
+
computeCost,
|
|
25
|
+
assessCostFromRows,
|
|
26
|
+
assessCostFromDb,
|
|
27
|
+
UnknownModelPricingError,
|
|
28
|
+
DEFAULT_PRICING
|
|
29
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
import {
|
|
3
|
+
DEFAULT_PRICING,
|
|
4
|
+
UnknownModelPricingError,
|
|
5
|
+
assessCostFromDb,
|
|
6
|
+
assessCostFromRows,
|
|
7
|
+
computeCost,
|
|
8
|
+
computeCostFromDb,
|
|
9
|
+
ensurePricingSeeded,
|
|
10
|
+
estimateCostFromRows,
|
|
11
|
+
getPricing,
|
|
12
|
+
getPricingFromDb,
|
|
13
|
+
getPricingFromRows,
|
|
14
|
+
normalizeModelName
|
|
15
|
+
} from "./index-2hwzmfgv.js";
|
|
16
|
+
import"./index-p35cyhc4.js";
|
|
17
|
+
export {
|
|
18
|
+
normalizeModelName,
|
|
19
|
+
getPricingFromRows,
|
|
20
|
+
getPricingFromDb,
|
|
21
|
+
getPricing,
|
|
22
|
+
estimateCostFromRows,
|
|
23
|
+
ensurePricingSeeded,
|
|
24
|
+
computeCostFromDb,
|
|
25
|
+
computeCost,
|
|
26
|
+
assessCostFromRows,
|
|
27
|
+
assessCostFromDb,
|
|
28
|
+
UnknownModelPricingError,
|
|
29
|
+
DEFAULT_PRICING
|
|
30
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
import {
|
|
3
|
+
DEFAULT_PRICING,
|
|
4
|
+
UnknownModelPricingError,
|
|
5
|
+
assessCostFromDb,
|
|
6
|
+
assessCostFromRows,
|
|
7
|
+
computeCost,
|
|
8
|
+
computeCostFromDb,
|
|
9
|
+
ensurePricingSeeded,
|
|
10
|
+
estimateCostFromRows,
|
|
11
|
+
getPricing,
|
|
12
|
+
getPricingFromDb,
|
|
13
|
+
getPricingFromRows,
|
|
14
|
+
normalizeModelName
|
|
15
|
+
} from "./index-ez9thsr7.js";
|
|
16
|
+
import"./index-p35cyhc4.js";
|
|
17
|
+
export {
|
|
18
|
+
normalizeModelName,
|
|
19
|
+
getPricingFromRows,
|
|
20
|
+
getPricingFromDb,
|
|
21
|
+
getPricing,
|
|
22
|
+
estimateCostFromRows,
|
|
23
|
+
ensurePricingSeeded,
|
|
24
|
+
computeCostFromDb,
|
|
25
|
+
computeCost,
|
|
26
|
+
assessCostFromRows,
|
|
27
|
+
assessCostFromDb,
|
|
28
|
+
UnknownModelPricingError,
|
|
29
|
+
DEFAULT_PRICING
|
|
30
|
+
};
|