@xfey/tutti 0.1.60 → 0.1.62

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.
Files changed (90) hide show
  1. package/README.md +2 -2
  2. package/dist/collaboration-state/clarification-records.d.ts +0 -9
  3. package/dist/collaboration-state/clarification-records.js +0 -57
  4. package/dist/collaboration-state/index.d.ts +6 -5
  5. package/dist/collaboration-state/index.js +5 -4
  6. package/dist/collaboration-state/messages.d.ts +4 -6
  7. package/dist/collaboration-state/messages.js +22 -29
  8. package/dist/collaboration-state/scratchpad-source-state.d.ts +4 -12
  9. package/dist/collaboration-state/scratchpad-source-state.js +24 -58
  10. package/dist/collaboration-state/scratchpad.d.ts +1 -2
  11. package/dist/collaboration-state/scratchpad.js +0 -59
  12. package/dist/collaboration-state/storage-types.d.ts +25 -5
  13. package/dist/collaboration-state/task-compile-context.d.ts +40 -0
  14. package/dist/collaboration-state/task-compile-context.js +273 -0
  15. package/dist/collaboration-state/types.d.ts +39 -9
  16. package/dist/control-plane/clarification-commands.d.ts +0 -1
  17. package/dist/control-plane/clarification-commands.js +0 -1
  18. package/dist/control-plane/index.js +16 -3
  19. package/dist/control-plane/project-context-bootstrap.d.ts +4 -4
  20. package/dist/control-plane/project-context-bootstrap.js +7 -12
  21. package/dist/control-plane/scratchpad-auto-refresh.js +12 -1
  22. package/dist/control-plane/scratchpad-refresh-start.js +77 -94
  23. package/dist/control-plane/scratchpad-refresh.js +3 -1
  24. package/dist/control-plane/scratchpad-source-messages.d.ts +15 -7
  25. package/dist/control-plane/scratchpad-source-messages.js +53 -69
  26. package/dist/control-plane/task-compile-clarification-context.d.ts +9 -0
  27. package/dist/control-plane/task-compile-clarification-context.js +70 -0
  28. package/dist/control-plane/task-compile-continuation.js +28 -56
  29. package/dist/control-plane/task-compile-output.js +50 -14
  30. package/dist/control-plane/task-compile-start.js +119 -55
  31. package/dist/control-plane/workflows/openai.d.ts +2 -1
  32. package/dist/control-plane/workflows/openai.js +13 -27
  33. package/dist/control-plane/workflows/types.d.ts +27 -17
  34. package/dist/server-shell/cli/args.d.ts +4 -0
  35. package/dist/server-shell/cli/args.js +11 -0
  36. package/dist/server-shell/cli/cli.js +35 -11
  37. package/dist/server-shell/cli/host-server-runtime.d.ts +2 -0
  38. package/dist/server-shell/cli/host-server-runtime.js +12 -5
  39. package/dist/server-shell/cli/local-control-client.d.ts +5 -1
  40. package/dist/server-shell/cli/local-control-client.js +19 -0
  41. package/dist/server-shell/desktop-integration/manager.d.ts +2 -0
  42. package/dist/server-shell/desktop-integration/manager.js +19 -31
  43. package/dist/server-shell/http/routes/local-control.d.ts +2 -0
  44. package/dist/server-shell/http/routes/local-control.js +24 -0
  45. package/dist/server-shell/local-console/invocation-context.js +10 -1
  46. package/dist/server-shell/local-console/operation-coordinator.d.ts +16 -0
  47. package/dist/server-shell/local-console/operation-coordinator.js +50 -0
  48. package/dist/server-shell/local-console/package-update-completion.d.ts +25 -0
  49. package/dist/server-shell/local-console/package-update-completion.js +91 -0
  50. package/dist/server-shell/local-console/package-update-hosts.d.ts +17 -0
  51. package/dist/server-shell/local-console/package-update-hosts.js +92 -0
  52. package/dist/server-shell/local-console/package-update-lock.d.ts +46 -0
  53. package/dist/server-shell/local-console/package-update-lock.js +223 -0
  54. package/dist/server-shell/local-console/package-update-log.d.ts +20 -0
  55. package/dist/server-shell/local-console/package-update-log.js +75 -0
  56. package/dist/server-shell/local-console/package-update-process.d.ts +22 -0
  57. package/dist/server-shell/local-console/package-update-process.js +252 -0
  58. package/dist/server-shell/local-console/package-update-record.d.ts +53 -0
  59. package/dist/server-shell/local-console/package-update-record.js +180 -0
  60. package/dist/server-shell/local-console/package-update-recovery.d.ts +34 -0
  61. package/dist/server-shell/local-console/package-update-recovery.js +140 -0
  62. package/dist/server-shell/local-console/package-update-service.d.ts +72 -0
  63. package/dist/server-shell/local-console/package-update-service.js +400 -0
  64. package/dist/server-shell/local-console/package-update-worker.d.ts +55 -0
  65. package/dist/server-shell/local-console/package-update-worker.js +297 -0
  66. package/dist/server-shell/local-console/project-service.d.ts +2 -0
  67. package/dist/server-shell/local-console/project-service.js +16 -12
  68. package/dist/server-shell/local-console/server.d.ts +9 -0
  69. package/dist/server-shell/local-console/server.js +92 -4
  70. package/dist/server-shell/package-installation/command.d.ts +36 -0
  71. package/dist/server-shell/package-installation/command.js +162 -0
  72. package/dist/server-shell/package-installation/discovery.d.ts +51 -0
  73. package/dist/server-shell/package-installation/discovery.js +137 -0
  74. package/dist/server-shell/package-installation/index.d.ts +5 -0
  75. package/dist/server-shell/package-installation/index.js +5 -0
  76. package/dist/server-shell/package-installation/installation.d.ts +34 -0
  77. package/dist/server-shell/package-installation/installation.js +123 -0
  78. package/dist/server-shell/package-installation/semver.d.ts +9 -0
  79. package/dist/server-shell/package-installation/semver.js +84 -0
  80. package/migrations/0014_scratchpad_task_compile_context.sql +147 -0
  81. package/migrations/README.md +2 -1
  82. package/package.json +1 -1
  83. package/prompts/procedures/README.md +3 -3
  84. package/prompts/procedures/scratchpad-refresh.md +17 -9
  85. package/prompts/procedures/task-compile.md +6 -3
  86. package/prompts/prompt-flow-map.md +21 -14
  87. package/web/assets/{homepage-motion-scene-CY7o4hnR.js → homepage-motion-scene-CRmrkkEH.js} +1 -1
  88. package/web/assets/{index-B08r3x8o.js → index-C9JrplHV.js} +14 -14
  89. package/web/assets/{index-DpabiRgp.css → index-IZKcmU_g.css} +1 -1
  90. package/web/index.html +2 -2
@@ -0,0 +1,162 @@
1
+ import { execFile, spawnSync } from "node:child_process";
2
+ import { accessSync, constants, existsSync, statSync } from "node:fs";
3
+ import { delimiter, dirname, isAbsolute, join } from "node:path";
4
+ const DEFAULT_COMMAND_TIMEOUT_MS = 10_000;
5
+ const DEFAULT_MAX_OUTPUT_BYTES = 16 * 1024;
6
+ const MAX_NPM_CANDIDATES = 12;
7
+ function bounded(value, maxBytes) {
8
+ const text = typeof value === "string" ? value : (value?.toString("utf8") ?? "");
9
+ return Buffer.byteLength(text, "utf8") <= maxBytes
10
+ ? text
11
+ : Buffer.from(text, "utf8").subarray(0, maxBytes).toString("utf8");
12
+ }
13
+ function classifyCommandError(code) {
14
+ if (code === "ETIMEDOUT") {
15
+ return "timeout";
16
+ }
17
+ if (code === "ENOBUFS") {
18
+ return "output_limit";
19
+ }
20
+ return "spawn_failed";
21
+ }
22
+ export const runPackageCommandSync = (plan) => {
23
+ const result = spawnSync(plan.executable, [...plan.args], {
24
+ encoding: "utf8",
25
+ env: plan.env,
26
+ maxBuffer: plan.maxOutputBytes,
27
+ shell: plan.shell,
28
+ stdio: ["ignore", "pipe", "pipe"],
29
+ timeout: plan.timeoutMs,
30
+ windowsHide: true,
31
+ });
32
+ return {
33
+ status: result.status,
34
+ stdout: bounded(result.stdout, plan.maxOutputBytes),
35
+ stderr: bounded(result.stderr, plan.maxOutputBytes),
36
+ ...(result.error === undefined
37
+ ? {}
38
+ : {
39
+ errorCode: classifyCommandError(result.error.code),
40
+ }),
41
+ };
42
+ };
43
+ export const runPackageCommand = async (plan) => await new Promise((resolveResult) => {
44
+ execFile(plan.executable, [...plan.args], {
45
+ encoding: "utf8",
46
+ env: plan.env,
47
+ maxBuffer: plan.maxOutputBytes,
48
+ shell: plan.shell,
49
+ timeout: plan.timeoutMs,
50
+ windowsHide: true,
51
+ }, (error, stdout, stderr) => {
52
+ const errorWithCode = error;
53
+ resolveResult({
54
+ status: errorWithCode === null
55
+ ? 0
56
+ : typeof errorWithCode.code === "number"
57
+ ? errorWithCode.code
58
+ : null,
59
+ stdout: bounded(stdout, plan.maxOutputBytes),
60
+ stderr: bounded(stderr, plan.maxOutputBytes),
61
+ ...(errorWithCode === null
62
+ ? {}
63
+ : {
64
+ errorCode: errorWithCode.killed === true
65
+ ? "timeout"
66
+ : classifyCommandError(errorWithCode.code),
67
+ }),
68
+ });
69
+ });
70
+ });
71
+ function executableCandidate(path) {
72
+ if (!isAbsolute(path) || !existsSync(path)) {
73
+ return false;
74
+ }
75
+ try {
76
+ if (!statSync(path).isFile()) {
77
+ return false;
78
+ }
79
+ accessSync(path, constants.X_OK);
80
+ return true;
81
+ }
82
+ catch {
83
+ return false;
84
+ }
85
+ }
86
+ function packagePrefixNpmCandidate(packageRoot, packageName) {
87
+ const segments = packageName.split("/").reverse();
88
+ let globalRoot = packageRoot;
89
+ for (const segment of segments) {
90
+ if (globalRoot.split(/[\\/]/u).at(-1) !== segment) {
91
+ return null;
92
+ }
93
+ globalRoot = dirname(globalRoot);
94
+ }
95
+ if (globalRoot.split(/[\\/]/u).at(-1) !== "node_modules") {
96
+ return null;
97
+ }
98
+ const libDirectory = dirname(globalRoot);
99
+ if (libDirectory.split(/[\\/]/u).at(-1) !== "lib") {
100
+ return null;
101
+ }
102
+ return join(dirname(libDirectory), "bin", "npm");
103
+ }
104
+ export function collectNpmExecutableCandidates(options) {
105
+ const collected = [];
106
+ const add = (candidate) => {
107
+ if (candidate !== null &&
108
+ collected.length < MAX_NPM_CANDIDATES &&
109
+ !collected.includes(candidate) &&
110
+ executableCandidate(candidate)) {
111
+ collected.push(candidate);
112
+ }
113
+ };
114
+ add(join(dirname(options.nodeExecutable), "npm"));
115
+ add(packagePrefixNpmCandidate(options.packageRoot, options.packageName));
116
+ for (const directory of (options.env.PATH ?? "").split(delimiter)) {
117
+ if (directory.trim() !== "" && isAbsolute(directory)) {
118
+ add(join(directory, "npm"));
119
+ }
120
+ }
121
+ return collected;
122
+ }
123
+ const SAFE_ENVIRONMENT_KEYS = [
124
+ "HOME",
125
+ "LANG",
126
+ "LC_ALL",
127
+ "LC_CTYPE",
128
+ "LOGNAME",
129
+ "NO_PROXY",
130
+ "TMPDIR",
131
+ "USER",
132
+ ];
133
+ export function createNpmCommandEnvironment(options) {
134
+ const result = {};
135
+ for (const key of SAFE_ENVIRONMENT_KEYS) {
136
+ const value = options.env[key];
137
+ if (value !== undefined && value !== "") {
138
+ result[key] = value;
139
+ }
140
+ }
141
+ const directories = [
142
+ dirname(options.nodeExecutable),
143
+ ...(options.env.PATH ?? "").split(delimiter).filter((entry) => isAbsolute(entry)),
144
+ ];
145
+ result.PATH = [...new Set(directories)].join(delimiter);
146
+ result.NPM_CONFIG_UPDATE_NOTIFIER = "false";
147
+ return result;
148
+ }
149
+ export function createPackageCommandPlan(options) {
150
+ if (!isAbsolute(options.executable)) {
151
+ throw new Error("Package commands require an absolute executable path.");
152
+ }
153
+ return {
154
+ executable: options.executable,
155
+ args: [...options.args],
156
+ env: { ...options.env },
157
+ timeoutMs: options.timeoutMs ?? DEFAULT_COMMAND_TIMEOUT_MS,
158
+ maxOutputBytes: options.maxOutputBytes ?? DEFAULT_MAX_OUTPUT_BYTES,
159
+ shell: false,
160
+ };
161
+ }
162
+ //# sourceMappingURL=command.js.map
@@ -0,0 +1,51 @@
1
+ import { type PackageCommandPlan, type PackageCommandRunner } from "./command.js";
2
+ import { PUBLISHED_TUTTI_PACKAGE_NAME, type GlobalPackageInstallation, type GlobalPackageInstallationResolution, type PackageInstallationUnavailableReason } from "./installation.js";
3
+ import { type ExactSemver } from "./semver.js";
4
+ export type PublishedPackageTarget = {
5
+ packageName: typeof PUBLISHED_TUTTI_PACKAGE_NAME;
6
+ version: ExactSemver;
7
+ };
8
+ export type PackageUpdateDiscovery = {
9
+ status: "up_to_date";
10
+ currentVersion: string;
11
+ latestVersion: string;
12
+ } | {
13
+ status: "update_available";
14
+ currentVersion: string;
15
+ latestVersion: string;
16
+ target: PublishedPackageTarget;
17
+ } | {
18
+ status: "unavailable";
19
+ currentVersion: string;
20
+ reasonCode: PackageInstallationUnavailableReason | "registry_unavailable";
21
+ };
22
+ export type GlobalInstallPlan = PackageCommandPlan & {
23
+ args: readonly [
24
+ "install",
25
+ "--global",
26
+ `${typeof PUBLISHED_TUTTI_PACKAGE_NAME}@${string}`,
27
+ "--no-audit",
28
+ "--no-fund"
29
+ ];
30
+ };
31
+ export declare function discoverPublishedPackageUpdate(options: {
32
+ installation: GlobalPackageInstallation;
33
+ runner?: PackageCommandRunner;
34
+ }): Promise<PackageUpdateDiscovery>;
35
+ export declare function buildGlobalInstallPlan(options: {
36
+ installation: GlobalPackageInstallation;
37
+ target: PublishedPackageTarget;
38
+ }): GlobalInstallPlan;
39
+ export declare class PackageUpdateDiscoveryService {
40
+ #private;
41
+ constructor(options?: {
42
+ runner?: PackageCommandRunner;
43
+ now?: () => number;
44
+ cacheTtlMs?: number;
45
+ });
46
+ discover(resolution: GlobalPackageInstallationResolution, options?: {
47
+ refresh?: boolean;
48
+ }): Promise<PackageUpdateDiscovery>;
49
+ clear(): void;
50
+ }
51
+ //# sourceMappingURL=discovery.d.ts.map
@@ -0,0 +1,137 @@
1
+ import { createPackageCommandPlan, runPackageCommand, } from "./command.js";
2
+ import { PUBLISHED_TUTTI_PACKAGE_NAME, } from "./installation.js";
3
+ import { compareExactSemver, parseExactSemver } from "./semver.js";
4
+ const DEFAULT_DISCOVERY_CACHE_TTL_MS = 5 * 60 * 1_000;
5
+ const GLOBAL_INSTALL_TIMEOUT_MS = 5 * 60 * 1_000;
6
+ export async function discoverPublishedPackageUpdate(options) {
7
+ const result = await (options.runner ?? runPackageCommand)(createPackageCommandPlan({
8
+ executable: options.installation.npmExecutable,
9
+ args: ["view", PUBLISHED_TUTTI_PACKAGE_NAME, "dist-tags.latest", "--json"],
10
+ env: options.installation.npmEnvironment,
11
+ }));
12
+ if (result.status !== 0) {
13
+ return {
14
+ status: "unavailable",
15
+ currentVersion: options.installation.currentVersion,
16
+ reasonCode: "registry_unavailable",
17
+ };
18
+ }
19
+ let parsed;
20
+ try {
21
+ parsed = JSON.parse(result.stdout);
22
+ }
23
+ catch {
24
+ parsed = null;
25
+ }
26
+ const latestVersion = parseExactSemver(parsed);
27
+ if (latestVersion === null) {
28
+ return {
29
+ status: "unavailable",
30
+ currentVersion: options.installation.currentVersion,
31
+ reasonCode: "registry_unavailable",
32
+ };
33
+ }
34
+ const comparison = compareExactSemver(latestVersion, options.installation.currentVersion);
35
+ if (comparison < 0) {
36
+ return {
37
+ status: "unavailable",
38
+ currentVersion: options.installation.currentVersion,
39
+ reasonCode: "registry_unavailable",
40
+ };
41
+ }
42
+ if (comparison === 0) {
43
+ return {
44
+ status: "up_to_date",
45
+ currentVersion: options.installation.currentVersion,
46
+ latestVersion,
47
+ };
48
+ }
49
+ return {
50
+ status: "update_available",
51
+ currentVersion: options.installation.currentVersion,
52
+ latestVersion,
53
+ target: { packageName: PUBLISHED_TUTTI_PACKAGE_NAME, version: latestVersion },
54
+ };
55
+ }
56
+ export function buildGlobalInstallPlan(options) {
57
+ if (options.target.packageName !== PUBLISHED_TUTTI_PACKAGE_NAME) {
58
+ throw new Error("The package update target is invalid.");
59
+ }
60
+ const version = parseExactSemver(options.target.version);
61
+ if (version === null || compareExactSemver(version, options.installation.currentVersion) <= 0) {
62
+ throw new Error("The package update target must be a newer exact version.");
63
+ }
64
+ const plan = createPackageCommandPlan({
65
+ executable: options.installation.npmExecutable,
66
+ args: [
67
+ "install",
68
+ "--global",
69
+ `${PUBLISHED_TUTTI_PACKAGE_NAME}@${version}`,
70
+ "--no-audit",
71
+ "--no-fund",
72
+ ],
73
+ env: options.installation.npmEnvironment,
74
+ timeoutMs: GLOBAL_INSTALL_TIMEOUT_MS,
75
+ });
76
+ return {
77
+ ...plan,
78
+ args: [
79
+ "install",
80
+ "--global",
81
+ `${PUBLISHED_TUTTI_PACKAGE_NAME}@${version}`,
82
+ "--no-audit",
83
+ "--no-fund",
84
+ ],
85
+ };
86
+ }
87
+ export class PackageUpdateDiscoveryService {
88
+ #runner;
89
+ #now;
90
+ #cacheTtlMs;
91
+ #cache;
92
+ #pending;
93
+ constructor(options = {}) {
94
+ this.#runner = options.runner ?? runPackageCommand;
95
+ this.#now = options.now ?? Date.now;
96
+ this.#cacheTtlMs = options.cacheTtlMs ?? DEFAULT_DISCOVERY_CACHE_TTL_MS;
97
+ }
98
+ async discover(resolution, options = {}) {
99
+ if (resolution.status === "unavailable") {
100
+ return {
101
+ status: "unavailable",
102
+ currentVersion: "0.0.0",
103
+ reasonCode: resolution.reasonCode,
104
+ };
105
+ }
106
+ const installation = resolution.installation;
107
+ const key = [
108
+ installation.npmExecutable,
109
+ installation.globalRoot,
110
+ installation.currentVersion,
111
+ ].join("\n");
112
+ if (options.refresh !== true &&
113
+ this.#cache?.key === key &&
114
+ this.#cache.expiresAt > this.#now()) {
115
+ return this.#cache.result;
116
+ }
117
+ if (options.refresh !== true && this.#pending?.key === key) {
118
+ return await this.#pending.result;
119
+ }
120
+ const result = discoverPublishedPackageUpdate({ installation, runner: this.#runner });
121
+ this.#pending = { key, result };
122
+ try {
123
+ const discovered = await result;
124
+ this.#cache = { key, expiresAt: this.#now() + this.#cacheTtlMs, result: discovered };
125
+ return discovered;
126
+ }
127
+ finally {
128
+ if (this.#pending?.result === result) {
129
+ this.#pending = undefined;
130
+ }
131
+ }
132
+ }
133
+ clear() {
134
+ this.#cache = undefined;
135
+ }
136
+ }
137
+ //# sourceMappingURL=discovery.js.map
@@ -0,0 +1,5 @@
1
+ export * from "./command.js";
2
+ export * from "./discovery.js";
3
+ export * from "./installation.js";
4
+ export * from "./semver.js";
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,5 @@
1
+ export * from "./command.js";
2
+ export * from "./discovery.js";
3
+ export * from "./installation.js";
4
+ export * from "./semver.js";
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,34 @@
1
+ import type { CliPackageIdentity } from "../cli/version.js";
2
+ import { type SyncPackageCommandRunner } from "./command.js";
3
+ import { type ExactSemver } from "./semver.js";
4
+ export declare const PUBLISHED_TUTTI_PACKAGE_NAME = "@xfey/tutti";
5
+ export declare const DEFAULT_NODE_ENGINE = ">=24 <26";
6
+ export declare const DEFAULT_NPM_ENGINE = ">=11 <12";
7
+ export type PackageInstallationUnavailableReason = "installation_not_global" | "custom_home" | "npm_unavailable" | "npm_global_root_mismatch" | "npm_version_unsupported" | "global_prefix_read_only";
8
+ export type GlobalPackageInstallation = {
9
+ packageName: typeof PUBLISHED_TUTTI_PACKAGE_NAME;
10
+ currentVersion: ExactSemver;
11
+ packageRoot: string;
12
+ globalRoot: string;
13
+ npmExecutable: string;
14
+ nodeExecutable: string;
15
+ npmEnvironment: NodeJS.ProcessEnv;
16
+ };
17
+ export type GlobalPackageInstallationResolution = {
18
+ status: "eligible";
19
+ installation: GlobalPackageInstallation;
20
+ } | {
21
+ status: "unavailable";
22
+ reasonCode: PackageInstallationUnavailableReason;
23
+ };
24
+ export declare function resolveGlobalPackageInstallation(options: {
25
+ packageIdentity: CliPackageIdentity;
26
+ tuttiHome: string;
27
+ env: NodeJS.ProcessEnv;
28
+ nodeExecutable?: string;
29
+ nodeVersion?: string;
30
+ defaultTuttiHome?: string;
31
+ runner?: SyncPackageCommandRunner;
32
+ pathWritable?: (path: string) => boolean;
33
+ }): GlobalPackageInstallationResolution;
34
+ //# sourceMappingURL=installation.d.ts.map
@@ -0,0 +1,123 @@
1
+ import { accessSync, constants, existsSync, lstatSync, realpathSync } from "node:fs";
2
+ import { homedir } from "node:os";
3
+ import { dirname, isAbsolute, join, resolve } from "node:path";
4
+ import { collectNpmExecutableCandidates, createNpmCommandEnvironment, createPackageCommandPlan, runPackageCommandSync, } from "./command.js";
5
+ import { parseExactSemver, semverMajor } from "./semver.js";
6
+ export const PUBLISHED_TUTTI_PACKAGE_NAME = "@xfey/tutti";
7
+ export const DEFAULT_NODE_ENGINE = ">=24 <26";
8
+ export const DEFAULT_NPM_ENGINE = ">=11 <12";
9
+ function defaultTuttiHome() {
10
+ return join(homedir(), ".tutti");
11
+ }
12
+ function isWritable(path) {
13
+ try {
14
+ accessSync(path, constants.W_OK);
15
+ return true;
16
+ }
17
+ catch {
18
+ return false;
19
+ }
20
+ }
21
+ function hasSupportedEngine(version, minimumMajor, maximumMajor) {
22
+ const exact = parseExactSemver(version.startsWith("v") ? version.slice(1) : version);
23
+ if (exact === null) {
24
+ return false;
25
+ }
26
+ const major = semverMajor(exact);
27
+ return major !== null && major >= minimumMajor && major < maximumMajor;
28
+ }
29
+ function strictAbsoluteLine(value) {
30
+ const trimmed = value.trim();
31
+ return trimmed !== "" && !trimmed.includes("\n") && !trimmed.includes("\r") && isAbsolute(trimmed)
32
+ ? trimmed
33
+ : null;
34
+ }
35
+ export function resolveGlobalPackageInstallation(options) {
36
+ const packageRoot = resolve(options.packageIdentity.root);
37
+ const currentVersion = parseExactSemver(options.packageIdentity.version);
38
+ if (options.packageIdentity.name !== PUBLISHED_TUTTI_PACKAGE_NAME ||
39
+ currentVersion === null ||
40
+ !isAbsolute(packageRoot)) {
41
+ return { status: "unavailable", reasonCode: "installation_not_global" };
42
+ }
43
+ if (resolve(options.tuttiHome) !== resolve(options.defaultTuttiHome ?? defaultTuttiHome())) {
44
+ return { status: "unavailable", reasonCode: "custom_home" };
45
+ }
46
+ const nodeExecutable = resolve(options.nodeExecutable ?? process.execPath);
47
+ if (!hasSupportedEngine(options.nodeVersion ?? process.version, 24, 26)) {
48
+ return { status: "unavailable", reasonCode: "npm_version_unsupported" };
49
+ }
50
+ const npmEnvironment = createNpmCommandEnvironment({ env: options.env, nodeExecutable });
51
+ const candidates = collectNpmExecutableCandidates({
52
+ env: options.env,
53
+ nodeExecutable,
54
+ packageRoot,
55
+ packageName: PUBLISHED_TUTTI_PACKAGE_NAME,
56
+ });
57
+ if (candidates.length === 0) {
58
+ return { status: "unavailable", reasonCode: "npm_unavailable" };
59
+ }
60
+ const runner = options.runner ?? runPackageCommandSync;
61
+ let observedGlobalRoot = false;
62
+ for (const npmExecutable of candidates) {
63
+ const rootResult = runner(createPackageCommandPlan({
64
+ executable: npmExecutable,
65
+ args: ["root", "--global"],
66
+ env: npmEnvironment,
67
+ }));
68
+ const globalRoot = rootResult.status === 0 ? strictAbsoluteLine(rootResult.stdout) : null;
69
+ if (globalRoot === null) {
70
+ continue;
71
+ }
72
+ observedGlobalRoot = true;
73
+ const expectedPackageRoot = join(globalRoot, ...PUBLISHED_TUTTI_PACKAGE_NAME.split("/"));
74
+ if (!existsSync(expectedPackageRoot)) {
75
+ continue;
76
+ }
77
+ try {
78
+ const expectedRealpath = realpathSync(expectedPackageRoot);
79
+ const packageRealpath = realpathSync(packageRoot);
80
+ if (lstatSync(expectedPackageRoot).isSymbolicLink()) {
81
+ if (expectedRealpath === packageRealpath) {
82
+ return { status: "unavailable", reasonCode: "installation_not_global" };
83
+ }
84
+ continue;
85
+ }
86
+ if (expectedRealpath !== packageRealpath) {
87
+ continue;
88
+ }
89
+ }
90
+ catch {
91
+ continue;
92
+ }
93
+ const versionResult = runner(createPackageCommandPlan({
94
+ executable: npmExecutable,
95
+ args: ["--version"],
96
+ env: npmEnvironment,
97
+ }));
98
+ if (versionResult.status !== 0 || !hasSupportedEngine(versionResult.stdout.trim(), 11, 12)) {
99
+ return { status: "unavailable", reasonCode: "npm_version_unsupported" };
100
+ }
101
+ const pathWritable = options.pathWritable ?? isWritable;
102
+ if (!pathWritable(globalRoot) || !pathWritable(dirname(expectedPackageRoot))) {
103
+ return { status: "unavailable", reasonCode: "global_prefix_read_only" };
104
+ }
105
+ return {
106
+ status: "eligible",
107
+ installation: {
108
+ packageName: PUBLISHED_TUTTI_PACKAGE_NAME,
109
+ currentVersion,
110
+ packageRoot,
111
+ globalRoot,
112
+ npmExecutable,
113
+ nodeExecutable,
114
+ npmEnvironment,
115
+ },
116
+ };
117
+ }
118
+ return {
119
+ status: "unavailable",
120
+ reasonCode: observedGlobalRoot ? "npm_global_root_mismatch" : "npm_unavailable",
121
+ };
122
+ }
123
+ //# sourceMappingURL=installation.js.map
@@ -0,0 +1,9 @@
1
+ declare const exactSemverBrand: unique symbol;
2
+ export type ExactSemver = string & {
3
+ readonly [exactSemverBrand]: true;
4
+ };
5
+ export declare function parseExactSemver(value: unknown): ExactSemver | null;
6
+ export declare function compareExactSemver(left: ExactSemver, right: ExactSemver): number;
7
+ export declare function semverMajor(version: ExactSemver): number | null;
8
+ export {};
9
+ //# sourceMappingURL=semver.d.ts.map
@@ -0,0 +1,84 @@
1
+ const NUMERIC_IDENTIFIER = "(?:0|[1-9]\\d*)";
2
+ const NON_NUMERIC_IDENTIFIER = "(?:\\d*[A-Za-z-][0-9A-Za-z-]*)";
3
+ const PRERELEASE_IDENTIFIER = `(?:${NUMERIC_IDENTIFIER}|${NON_NUMERIC_IDENTIFIER})`;
4
+ const EXACT_SEMVER_PATTERN = new RegExp(`^(${NUMERIC_IDENTIFIER})\\.(${NUMERIC_IDENTIFIER})\\.(${NUMERIC_IDENTIFIER})` +
5
+ `(?:-(${PRERELEASE_IDENTIFIER}(?:\\.${PRERELEASE_IDENTIFIER})*))?` +
6
+ "(?:\\+([0-9A-Za-z-]+(?:\\.[0-9A-Za-z-]+)*))?$", "u");
7
+ function parse(value) {
8
+ const match = EXACT_SEMVER_PATTERN.exec(value);
9
+ if (match === null) {
10
+ return null;
11
+ }
12
+ const major = match[1];
13
+ const minor = match[2];
14
+ const patch = match[3];
15
+ if (major === undefined || minor === undefined || patch === undefined) {
16
+ return null;
17
+ }
18
+ return {
19
+ raw: value,
20
+ core: [major, minor, patch],
21
+ prerelease: match[4] === undefined ? [] : match[4].split("."),
22
+ };
23
+ }
24
+ export function parseExactSemver(value) {
25
+ if (typeof value !== "string" || value.trim() !== value) {
26
+ return null;
27
+ }
28
+ return parse(value)?.raw ?? null;
29
+ }
30
+ function compareNumeric(left, right) {
31
+ if (left.length !== right.length) {
32
+ return left.length < right.length ? -1 : 1;
33
+ }
34
+ return left === right ? 0 : left < right ? -1 : 1;
35
+ }
36
+ export function compareExactSemver(left, right) {
37
+ const parsedLeft = parse(left);
38
+ const parsedRight = parse(right);
39
+ if (parsedLeft === null || parsedRight === null) {
40
+ throw new Error("Cannot compare invalid SemVer values.");
41
+ }
42
+ for (let index = 0; index < parsedLeft.core.length; index += 1) {
43
+ const comparison = compareNumeric(parsedLeft.core[index] ?? "", parsedRight.core[index] ?? "");
44
+ if (comparison !== 0) {
45
+ return comparison;
46
+ }
47
+ }
48
+ if (parsedLeft.prerelease.length === 0 || parsedRight.prerelease.length === 0) {
49
+ if (parsedLeft.prerelease.length === parsedRight.prerelease.length) {
50
+ return 0;
51
+ }
52
+ return parsedLeft.prerelease.length === 0 ? 1 : -1;
53
+ }
54
+ const length = Math.max(parsedLeft.prerelease.length, parsedRight.prerelease.length);
55
+ for (let index = 0; index < length; index += 1) {
56
+ const leftIdentifier = parsedLeft.prerelease[index];
57
+ const rightIdentifier = parsedRight.prerelease[index];
58
+ if (leftIdentifier === undefined || rightIdentifier === undefined) {
59
+ return leftIdentifier === undefined ? -1 : 1;
60
+ }
61
+ if (leftIdentifier === rightIdentifier) {
62
+ continue;
63
+ }
64
+ const leftNumeric = /^\d+$/u.test(leftIdentifier);
65
+ const rightNumeric = /^\d+$/u.test(rightIdentifier);
66
+ if (leftNumeric && rightNumeric) {
67
+ return compareNumeric(leftIdentifier, rightIdentifier);
68
+ }
69
+ if (leftNumeric !== rightNumeric) {
70
+ return leftNumeric ? -1 : 1;
71
+ }
72
+ return leftIdentifier < rightIdentifier ? -1 : 1;
73
+ }
74
+ return 0;
75
+ }
76
+ export function semverMajor(version) {
77
+ const major = parse(version)?.core[0];
78
+ if (major === undefined || major.length > 6) {
79
+ return null;
80
+ }
81
+ const parsed = Number.parseInt(major, 10);
82
+ return Number.isSafeInteger(parsed) ? parsed : null;
83
+ }
84
+ //# sourceMappingURL=semver.js.map