@xfey/tutti 0.1.54 → 0.1.55

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 (36) hide show
  1. package/README.md +5 -2
  2. package/desktop-assets/README.md +13 -0
  3. package/desktop-assets/Tutti-square.png +0 -0
  4. package/desktop-assets/Tutti.icns +0 -0
  5. package/desktop-assets/macos-handler.applescript +24 -0
  6. package/dist/server-shell/cli/args.d.ts +7 -0
  7. package/dist/server-shell/cli/args.js +32 -0
  8. package/dist/server-shell/cli/cli.js +76 -1
  9. package/dist/server-shell/cli/errors.d.ts +1 -1
  10. package/dist/server-shell/cli/version.d.ts +6 -0
  11. package/dist/server-shell/cli/version.js +11 -2
  12. package/dist/server-shell/desktop-integration/index.d.ts +5 -0
  13. package/dist/server-shell/desktop-integration/index.js +5 -0
  14. package/dist/server-shell/desktop-integration/linux.d.ts +12 -0
  15. package/dist/server-shell/desktop-integration/linux.js +186 -0
  16. package/dist/server-shell/desktop-integration/macos.d.ts +9 -0
  17. package/dist/server-shell/desktop-integration/macos.js +138 -0
  18. package/dist/server-shell/desktop-integration/manager.d.ts +40 -0
  19. package/dist/server-shell/desktop-integration/manager.js +223 -0
  20. package/dist/server-shell/desktop-integration/platform.d.ts +23 -0
  21. package/dist/server-shell/desktop-integration/platform.js +21 -0
  22. package/dist/server-shell/desktop-integration/protocol.d.ts +9 -0
  23. package/dist/server-shell/desktop-integration/protocol.js +28 -0
  24. package/dist/server-shell/desktop-integration/record.d.ts +17 -0
  25. package/dist/server-shell/desktop-integration/record.js +74 -0
  26. package/dist/server-shell/local-console/invocation-context.d.ts +7 -0
  27. package/dist/server-shell/local-console/invocation-context.js +8 -9
  28. package/dist/server-shell/local-console/managed-console.d.ts +1 -0
  29. package/dist/server-shell/local-console/managed-console.js +10 -5
  30. package/dist/server-shell/local-console/project-service.js +1 -1
  31. package/dist/server-shell/local-console/server.js +23 -6
  32. package/package.json +2 -1
  33. package/web/assets/{homepage-motion-scene-C96Na8TV.js → homepage-motion-scene-D0-wLq3D.js} +1 -1
  34. package/web/assets/{index-CM41hrQd.js → index-BAsLSZ5U.js} +14 -14
  35. package/web/assets/{index-nWy1QP97.css → index-DMaiDZ9k.css} +1 -1
  36. package/web/index.html +2 -2
@@ -0,0 +1,138 @@
1
+ import { copyFileSync, existsSync, mkdirSync, mkdtempSync, readFileSync, renameSync, rmSync, writeFileSync, } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { requireDesktopCommand, runDesktopCommand, } from "./platform.js";
4
+ const BUNDLE_IDENTIFIER = "now.tutti.desktop";
5
+ const LAUNCH_SERVICES_REGISTER = "/System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister";
6
+ function plistValue(runner, plistPath, key) {
7
+ const result = runner("/usr/bin/plutil", ["-extract", key, "raw", "-o", "-", plistPath]);
8
+ return result.status === 0 ? result.stdout.trim() : null;
9
+ }
10
+ export function renderMacosHandlerTemplate(template, identity) {
11
+ const nodeToken = "__TUTTI_NODE_EXECUTABLE_STRING__";
12
+ const cliToken = "__TUTTI_CLI_ENTRYPOINT_STRING__";
13
+ if (!template.includes(nodeToken) || !template.includes(cliToken)) {
14
+ throw new Error("The packaged macOS desktop handler template is invalid.");
15
+ }
16
+ return template
17
+ .replace(nodeToken, JSON.stringify(identity.nodeExecutable))
18
+ .replace(cliToken, JSON.stringify(identity.cliEntrypoint));
19
+ }
20
+ export function createMacosDesktopAdapter(options) {
21
+ const runner = options.runner ?? runDesktopCommand;
22
+ const applicationsDir = join(options.homeDir, "Applications");
23
+ const entryPath = join(applicationsDir, "Tutti.app");
24
+ const templatePath = join(options.assetRoot, "macos-handler.applescript");
25
+ const iconPath = join(options.assetRoot, "Tutti.icns");
26
+ const inspect = () => {
27
+ if (!existsSync(entryPath)) {
28
+ return "missing";
29
+ }
30
+ const plistPath = join(entryPath, "Contents", "Info.plist");
31
+ if (!existsSync(plistPath)) {
32
+ return "conflict";
33
+ }
34
+ const identifier = plistValue(runner, plistPath, "CFBundleIdentifier");
35
+ const managed = plistValue(runner, plistPath, "TuttiManaged");
36
+ const schemaVersion = plistValue(runner, plistPath, "TuttiSchemaVersion");
37
+ return identifier === BUNDLE_IDENTIFIER && managed === "true" && schemaVersion === "1"
38
+ ? "owned"
39
+ : "conflict";
40
+ };
41
+ return {
42
+ platform: "darwin",
43
+ entryPath,
44
+ inspect,
45
+ install(identity) {
46
+ if (inspect() === "conflict") {
47
+ throw new Error("An application not managed by Tutti already occupies the desktop entry.");
48
+ }
49
+ if (!existsSync(templatePath) || !existsSync(iconPath)) {
50
+ throw new Error("The packaged macOS desktop assets are unavailable.");
51
+ }
52
+ mkdirSync(applicationsDir, { recursive: true });
53
+ const temporaryRoot = mkdtempSync(join(applicationsDir, ".tutti-desktop-"));
54
+ const sourcePath = join(temporaryRoot, "handler.applescript");
55
+ const compiledPath = join(temporaryRoot, "Tutti.app");
56
+ const previousPath = join(temporaryRoot, "Tutti.previous.app");
57
+ let previousMoved = false;
58
+ let installedMoved = false;
59
+ try {
60
+ const template = readFileSync(templatePath, "utf8");
61
+ writeFileSync(sourcePath, renderMacosHandlerTemplate(template, identity), {
62
+ encoding: "utf8",
63
+ mode: 0o600,
64
+ });
65
+ requireDesktopCommand(runner, "/usr/bin/osacompile", ["-l", "AppleScript", "-o", compiledPath, sourcePath], "macOS desktop handler compilation");
66
+ const plistPath = join(compiledPath, "Contents", "Info.plist");
67
+ const resourcesPath = join(compiledPath, "Contents", "Resources");
68
+ mkdirSync(resourcesPath, { recursive: true });
69
+ copyFileSync(iconPath, join(resourcesPath, "Tutti.icns"));
70
+ const replaceString = (key, value) => requireDesktopCommand(runner, "/usr/bin/plutil", ["-replace", key, "-string", value, plistPath], `macOS ${key} metadata update`);
71
+ replaceString("CFBundleIdentifier", BUNDLE_IDENTIFIER);
72
+ replaceString("CFBundleName", "Tutti");
73
+ replaceString("CFBundleDisplayName", "Tutti");
74
+ replaceString("CFBundleIconFile", "Tutti.icns");
75
+ requireDesktopCommand(runner, "/usr/bin/plutil", ["-insert", "LSUIElement", "-bool", "YES", plistPath], "macOS background app metadata update");
76
+ requireDesktopCommand(runner, "/usr/bin/plutil", ["-insert", "TuttiManaged", "-bool", "YES", plistPath], "macOS ownership metadata update");
77
+ requireDesktopCommand(runner, "/usr/bin/plutil", ["-insert", "TuttiSchemaVersion", "-integer", "1", plistPath], "macOS schema metadata update");
78
+ requireDesktopCommand(runner, "/usr/bin/plutil", [
79
+ "-insert",
80
+ "CFBundleURLTypes",
81
+ "-json",
82
+ JSON.stringify([
83
+ {
84
+ CFBundleTypeRole: "Viewer",
85
+ CFBundleURLName: BUNDLE_IDENTIFIER,
86
+ CFBundleURLSchemes: ["tutti"],
87
+ },
88
+ ]),
89
+ plistPath,
90
+ ], "macOS URL scheme metadata update");
91
+ requireDesktopCommand(runner, "/usr/bin/codesign", ["--force", "--sign", "-", compiledPath], "macOS desktop handler signing");
92
+ if (existsSync(entryPath)) {
93
+ renameSync(entryPath, previousPath);
94
+ previousMoved = true;
95
+ }
96
+ renameSync(compiledPath, entryPath);
97
+ installedMoved = true;
98
+ requireDesktopCommand(runner, LAUNCH_SERVICES_REGISTER, ["-f", entryPath], "macOS URL scheme registration");
99
+ if (previousMoved) {
100
+ rmSync(previousPath, { recursive: true, force: true });
101
+ previousMoved = false;
102
+ }
103
+ }
104
+ catch (error) {
105
+ if (installedMoved && existsSync(entryPath)) {
106
+ rmSync(entryPath, { recursive: true, force: true });
107
+ }
108
+ if (previousMoved && existsSync(previousPath)) {
109
+ renameSync(previousPath, entryPath);
110
+ }
111
+ throw error;
112
+ }
113
+ finally {
114
+ rmSync(temporaryRoot, { recursive: true, force: true });
115
+ }
116
+ },
117
+ remove() {
118
+ const inspection = inspect();
119
+ if (inspection === "conflict") {
120
+ throw new Error("The existing macOS desktop entry is not managed by Tutti.");
121
+ }
122
+ if (inspection === "missing") {
123
+ return;
124
+ }
125
+ runner(LAUNCH_SERVICES_REGISTER, ["-u", entryPath]);
126
+ rmSync(entryPath, { recursive: true, force: true });
127
+ },
128
+ };
129
+ }
130
+ export function macosDesktopRequirementsAvailable(assetRoot) {
131
+ return (existsSync("/usr/bin/osacompile") &&
132
+ existsSync("/usr/bin/plutil") &&
133
+ existsSync("/usr/bin/codesign") &&
134
+ existsSync(LAUNCH_SERVICES_REGISTER) &&
135
+ existsSync(join(assetRoot, "macos-handler.applescript")) &&
136
+ existsSync(join(assetRoot, "Tutti.icns")));
137
+ }
138
+ //# sourceMappingURL=macos.js.map
@@ -0,0 +1,40 @@
1
+ import { type DesktopCommandRunner, type DesktopInstallationIdentity, type DesktopPlatformAdapter } from "./platform.js";
2
+ export type DesktopIntegrationResult = {
3
+ status: "installed";
4
+ entry_path: string;
5
+ changed: boolean;
6
+ } | {
7
+ status: "removed";
8
+ entry_path: string;
9
+ changed: boolean;
10
+ } | {
11
+ status: "unavailable";
12
+ reason_code: "unsupported_platform" | "package_not_global" | "custom_tutti_home" | "headless" | "requirements_missing" | "record_invalid" | "entry_conflict" | "operation_failed";
13
+ reason: string;
14
+ entry_path?: string;
15
+ } | {
16
+ status: "skipped";
17
+ reason_code: "not_eligible" | "removed";
18
+ };
19
+ type ManagerContext = {
20
+ tuttiHome: string;
21
+ adapter: DesktopPlatformAdapter;
22
+ identity: DesktopInstallationIdentity;
23
+ now: () => Date;
24
+ };
25
+ export declare function ensureDesktopIntegration(context: ManagerContext): DesktopIntegrationResult;
26
+ export declare function installDesktopIntegration(context: ManagerContext): DesktopIntegrationResult;
27
+ export declare function inspectDesktopIntegration(context: ManagerContext): DesktopIntegrationResult;
28
+ export declare function removeDesktopIntegration(context: ManagerContext): DesktopIntegrationResult;
29
+ declare function createRealContext(options: {
30
+ cwd?: string;
31
+ env?: NodeJS.ProcessEnv;
32
+ platform?: NodeJS.Platform;
33
+ now?: () => Date;
34
+ runner?: DesktopCommandRunner;
35
+ requireStableGlobal?: boolean;
36
+ }): ManagerContext | DesktopIntegrationResult;
37
+ export declare function ensureDesktopIntegrationAutomatically(options?: Parameters<typeof createRealContext>[0]): DesktopIntegrationResult;
38
+ export declare function runDesktopIntegrationCommand(action: "install" | "status" | "remove", options?: Parameters<typeof createRealContext>[0]): DesktopIntegrationResult;
39
+ export {};
40
+ //# sourceMappingURL=manager.d.ts.map
@@ -0,0 +1,223 @@
1
+ import { existsSync, realpathSync } from "node:fs";
2
+ import { homedir } from "node:os";
3
+ import { dirname, isAbsolute, join, resolve } from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ import { resolveTuttiHome } from "../../providers/openai/index.js";
6
+ import { readCliPackageIdentity } from "../cli/version.js";
7
+ import { createLinuxDesktopAdapter, findDesktopExecutable, linuxDesktopRequirementsAvailable, } from "./linux.js";
8
+ import { createMacosDesktopAdapter, macosDesktopRequirementsAvailable } from "./macos.js";
9
+ import { runDesktopCommand, } from "./platform.js";
10
+ import { readDesktopIntegrationRecord, writeDesktopIntegrationRecord, } from "./record.js";
11
+ const PUBLISHED_CLI_PACKAGE_NAME = "@xfey/tutti";
12
+ function desktopAssetRoot() {
13
+ return resolve(dirname(fileURLToPath(import.meta.url)), "../../../desktop-assets");
14
+ }
15
+ function readRecordSafely(tuttiHome) {
16
+ try {
17
+ return { kind: "ok", record: readDesktopIntegrationRecord(tuttiHome) };
18
+ }
19
+ catch {
20
+ return { kind: "invalid" };
21
+ }
22
+ }
23
+ function recordFor(context, state) {
24
+ return {
25
+ schema_version: 1,
26
+ state,
27
+ platform: context.adapter.platform,
28
+ entry_path: context.adapter.entryPath,
29
+ protocol: "tutti",
30
+ package_version: context.identity.packageVersion,
31
+ node_executable: context.identity.nodeExecutable,
32
+ cli_entrypoint: context.identity.cliEntrypoint,
33
+ updated_at: context.now().toISOString(),
34
+ };
35
+ }
36
+ function identityMatches(record, context) {
37
+ return (record.platform === context.adapter.platform &&
38
+ record.entry_path === context.adapter.entryPath &&
39
+ record.package_version === context.identity.packageVersion &&
40
+ record.node_executable === context.identity.nodeExecutable &&
41
+ record.cli_entrypoint === context.identity.cliEntrypoint);
42
+ }
43
+ function unavailable(reasonCode, reason, entryPath) {
44
+ return {
45
+ status: "unavailable",
46
+ reason_code: reasonCode,
47
+ reason,
48
+ ...(entryPath === undefined ? {} : { entry_path: entryPath }),
49
+ };
50
+ }
51
+ export function ensureDesktopIntegration(context) {
52
+ const read = readRecordSafely(context.tuttiHome);
53
+ if (read.kind === "invalid") {
54
+ return unavailable("record_invalid", "The desktop integration record is invalid and was not overwritten.", context.adapter.entryPath);
55
+ }
56
+ const record = read.record;
57
+ if (record?.state === "removed") {
58
+ return { status: "skipped", reason_code: "removed" };
59
+ }
60
+ let inspection;
61
+ try {
62
+ inspection = context.adapter.inspect();
63
+ }
64
+ catch {
65
+ return unavailable("operation_failed", "The desktop entry could not be inspected.", context.adapter.entryPath);
66
+ }
67
+ if (inspection === "conflict") {
68
+ return unavailable("entry_conflict", "The desktop entry path is occupied by an application not managed by Tutti.", context.adapter.entryPath);
69
+ }
70
+ if (record?.state === "installed" && inspection === "missing") {
71
+ writeDesktopIntegrationRecord(context.tuttiHome, recordFor(context, "removed"));
72
+ return { status: "removed", entry_path: context.adapter.entryPath, changed: true };
73
+ }
74
+ if (record?.state === "installed" && inspection === "owned" && identityMatches(record, context)) {
75
+ return { status: "installed", entry_path: context.adapter.entryPath, changed: false };
76
+ }
77
+ try {
78
+ context.adapter.install(context.identity);
79
+ writeDesktopIntegrationRecord(context.tuttiHome, recordFor(context, "installed"));
80
+ return { status: "installed", entry_path: context.adapter.entryPath, changed: true };
81
+ }
82
+ catch {
83
+ return unavailable("operation_failed", "Tutti could not install the user-level desktop entry.", context.adapter.entryPath);
84
+ }
85
+ }
86
+ export function installDesktopIntegration(context) {
87
+ const read = readRecordSafely(context.tuttiHome);
88
+ if (read.kind === "invalid") {
89
+ return unavailable("record_invalid", "The desktop integration record is invalid and was not overwritten.", context.adapter.entryPath);
90
+ }
91
+ try {
92
+ if (context.adapter.inspect() === "conflict") {
93
+ return unavailable("entry_conflict", "The desktop entry path is occupied by an application not managed by Tutti.", context.adapter.entryPath);
94
+ }
95
+ context.adapter.install(context.identity);
96
+ writeDesktopIntegrationRecord(context.tuttiHome, recordFor(context, "installed"));
97
+ return { status: "installed", entry_path: context.adapter.entryPath, changed: true };
98
+ }
99
+ catch {
100
+ return unavailable("operation_failed", "Tutti could not install or repair the user-level desktop entry.", context.adapter.entryPath);
101
+ }
102
+ }
103
+ export function inspectDesktopIntegration(context) {
104
+ const read = readRecordSafely(context.tuttiHome);
105
+ if (read.kind === "invalid") {
106
+ return unavailable("record_invalid", "The desktop integration record is invalid.", context.adapter.entryPath);
107
+ }
108
+ try {
109
+ const inspection = context.adapter.inspect();
110
+ if (inspection === "conflict") {
111
+ return unavailable("entry_conflict", "The desktop entry path is occupied by an application not managed by Tutti.", context.adapter.entryPath);
112
+ }
113
+ if (inspection === "owned" && read.record?.state !== "removed") {
114
+ return { status: "installed", entry_path: context.adapter.entryPath, changed: false };
115
+ }
116
+ return { status: "removed", entry_path: context.adapter.entryPath, changed: false };
117
+ }
118
+ catch {
119
+ return unavailable("operation_failed", "The desktop entry could not be inspected.", context.adapter.entryPath);
120
+ }
121
+ }
122
+ export function removeDesktopIntegration(context) {
123
+ const read = readRecordSafely(context.tuttiHome);
124
+ if (read.kind === "invalid") {
125
+ return unavailable("record_invalid", "The desktop integration record is invalid and the entry was not removed.", context.adapter.entryPath);
126
+ }
127
+ try {
128
+ if (context.adapter.inspect() === "conflict") {
129
+ return unavailable("entry_conflict", "The desktop entry is not managed by Tutti and was not removed.", context.adapter.entryPath);
130
+ }
131
+ context.adapter.remove();
132
+ writeDesktopIntegrationRecord(context.tuttiHome, recordFor(context, "removed"));
133
+ return { status: "removed", entry_path: context.adapter.entryPath, changed: true };
134
+ }
135
+ catch {
136
+ return unavailable("operation_failed", "Tutti could not remove the user-level desktop entry.", context.adapter.entryPath);
137
+ }
138
+ }
139
+ function stableGlobalPackage(options) {
140
+ const npmExecutable = findDesktopExecutable("npm", options.env);
141
+ if (npmExecutable === null) {
142
+ return false;
143
+ }
144
+ const result = options.runner(npmExecutable, ["root", "--global"]);
145
+ const globalRoot = result.stdout.trim();
146
+ if (result.status !== 0 || !isAbsolute(globalRoot)) {
147
+ return false;
148
+ }
149
+ const expectedRoot = join(globalRoot, ...PUBLISHED_CLI_PACKAGE_NAME.split("/"));
150
+ if (!existsSync(expectedRoot)) {
151
+ return false;
152
+ }
153
+ try {
154
+ return realpathSync(expectedRoot) === realpathSync(options.packageRoot);
155
+ }
156
+ catch {
157
+ return false;
158
+ }
159
+ }
160
+ function createRealContext(options) {
161
+ const cwd = resolve(options.cwd ?? process.cwd());
162
+ const env = options.env ?? process.env;
163
+ const platform = options.platform ?? process.platform;
164
+ const runner = options.runner ?? runDesktopCommand;
165
+ const packageIdentity = readCliPackageIdentity();
166
+ if (packageIdentity.name !== PUBLISHED_CLI_PACKAGE_NAME ||
167
+ (options.requireStableGlobal !== false &&
168
+ !stableGlobalPackage({ packageRoot: packageIdentity.root, env, runner }))) {
169
+ return unavailable("package_not_global", "Desktop integration requires a stable global @xfey/tutti installation.");
170
+ }
171
+ if (env.TUTTI_HOME !== undefined && env.TUTTI_HOME.trim() !== "") {
172
+ return unavailable("custom_tutti_home", "Desktop integration is available only for the default Tutti home.");
173
+ }
174
+ if (platform !== "darwin" && platform !== "linux") {
175
+ return unavailable("unsupported_platform", "Desktop integration supports macOS and Linux.");
176
+ }
177
+ const homeDir = env.HOME !== undefined && isAbsolute(env.HOME) ? env.HOME : homedir();
178
+ const assetRoot = desktopAssetRoot();
179
+ if ((platform === "darwin" && !macosDesktopRequirementsAvailable(assetRoot)) ||
180
+ (platform === "linux" && !linuxDesktopRequirementsAvailable(assetRoot, env))) {
181
+ return unavailable("requirements_missing", "The required user-level desktop registration tools are unavailable.");
182
+ }
183
+ const cliEntrypoint = process.argv[1];
184
+ if (cliEntrypoint === undefined || cliEntrypoint.trim() === "") {
185
+ return unavailable("requirements_missing", "The Tutti CLI entrypoint is unavailable.");
186
+ }
187
+ const adapter = platform === "darwin"
188
+ ? createMacosDesktopAdapter({ homeDir, assetRoot, runner })
189
+ : createLinuxDesktopAdapter({ homeDir, assetRoot, env, runner });
190
+ return {
191
+ tuttiHome: resolveTuttiHome(undefined, cwd),
192
+ adapter,
193
+ identity: {
194
+ packageVersion: packageIdentity.version,
195
+ nodeExecutable: resolve(process.execPath),
196
+ cliEntrypoint: resolve(cliEntrypoint),
197
+ },
198
+ now: options.now ?? (() => new Date()),
199
+ };
200
+ }
201
+ export function ensureDesktopIntegrationAutomatically(options = {}) {
202
+ const env = options.env ?? process.env;
203
+ const platform = options.platform ?? process.platform;
204
+ if (platform === "linux" && env.DISPLAY === undefined && env.WAYLAND_DISPLAY === undefined) {
205
+ return unavailable("headless", "No graphical Linux desktop session is available.");
206
+ }
207
+ const context = createRealContext(options);
208
+ return "adapter" in context ? ensureDesktopIntegration(context) : context;
209
+ }
210
+ export function runDesktopIntegrationCommand(action, options = {}) {
211
+ const context = createRealContext(options);
212
+ if (!("adapter" in context)) {
213
+ return context;
214
+ }
215
+ if (action === "status") {
216
+ return inspectDesktopIntegration(context);
217
+ }
218
+ if (action === "remove") {
219
+ return removeDesktopIntegration(context);
220
+ }
221
+ return installDesktopIntegration(context);
222
+ }
223
+ //# sourceMappingURL=manager.js.map
@@ -0,0 +1,23 @@
1
+ export type DesktopPlatform = "darwin" | "linux";
2
+ export type DesktopInstallationIdentity = {
3
+ packageVersion: string;
4
+ nodeExecutable: string;
5
+ cliEntrypoint: string;
6
+ };
7
+ export type DesktopEntryInspection = "missing" | "owned" | "conflict";
8
+ export type DesktopPlatformAdapter = {
9
+ platform: DesktopPlatform;
10
+ entryPath: string;
11
+ inspect: () => DesktopEntryInspection;
12
+ install: (identity: DesktopInstallationIdentity) => void;
13
+ remove: () => void;
14
+ };
15
+ export type DesktopCommandResult = {
16
+ status: number | null;
17
+ stdout: string;
18
+ stderr: string;
19
+ };
20
+ export type DesktopCommandRunner = (executable: string, args: readonly string[]) => DesktopCommandResult;
21
+ export declare const runDesktopCommand: DesktopCommandRunner;
22
+ export declare function requireDesktopCommand(runner: DesktopCommandRunner, executable: string, args: readonly string[], description: string): DesktopCommandResult;
23
+ //# sourceMappingURL=platform.d.ts.map
@@ -0,0 +1,21 @@
1
+ import { spawnSync } from "node:child_process";
2
+ export const runDesktopCommand = (executable, args) => {
3
+ const result = spawnSync(executable, [...args], {
4
+ encoding: "utf8",
5
+ shell: false,
6
+ stdio: ["ignore", "pipe", "pipe"],
7
+ });
8
+ return {
9
+ status: result.status,
10
+ stdout: result.stdout ?? "",
11
+ stderr: result.stderr ?? "",
12
+ };
13
+ };
14
+ export function requireDesktopCommand(runner, executable, args, description) {
15
+ const result = runner(executable, args);
16
+ if (result.status !== 0) {
17
+ throw new Error(`${description} failed.`);
18
+ }
19
+ return result;
20
+ }
21
+ //# sourceMappingURL=platform.js.map
@@ -0,0 +1,9 @@
1
+ export declare const TUTTI_DESKTOP_PROJECTS_URL = "tutti://projects";
2
+ export type TuttiDesktopIntent = {
3
+ kind: "projects";
4
+ };
5
+ export declare class TuttiDesktopProtocolError extends Error {
6
+ constructor();
7
+ }
8
+ export declare function parseTuttiDesktopUrl(value: string): TuttiDesktopIntent;
9
+ //# sourceMappingURL=protocol.d.ts.map
@@ -0,0 +1,28 @@
1
+ export const TUTTI_DESKTOP_PROJECTS_URL = "tutti://projects";
2
+ export class TuttiDesktopProtocolError extends Error {
3
+ constructor() {
4
+ super("Only the fixed tutti://projects desktop destination is supported.");
5
+ this.name = "TuttiDesktopProtocolError";
6
+ }
7
+ }
8
+ export function parseTuttiDesktopUrl(value) {
9
+ let url;
10
+ try {
11
+ url = new URL(value);
12
+ }
13
+ catch {
14
+ throw new TuttiDesktopProtocolError();
15
+ }
16
+ if (url.protocol !== "tutti:" ||
17
+ url.hostname !== "projects" ||
18
+ (url.pathname !== "" && url.pathname !== "/") ||
19
+ url.username !== "" ||
20
+ url.password !== "" ||
21
+ url.port !== "" ||
22
+ url.search !== "" ||
23
+ url.hash !== "") {
24
+ throw new TuttiDesktopProtocolError();
25
+ }
26
+ return { kind: "projects" };
27
+ }
28
+ //# sourceMappingURL=protocol.js.map
@@ -0,0 +1,17 @@
1
+ import type { DesktopPlatform } from "./platform.js";
2
+ export type DesktopIntegrationRecord = {
3
+ schema_version: 1;
4
+ state: "installed" | "removed";
5
+ platform: DesktopPlatform;
6
+ entry_path: string;
7
+ protocol: "tutti";
8
+ package_version: string;
9
+ node_executable: string;
10
+ cli_entrypoint: string;
11
+ updated_at: string;
12
+ };
13
+ export declare function parseDesktopIntegrationRecord(value: unknown): DesktopIntegrationRecord;
14
+ export declare function desktopIntegrationRecordPath(tuttiHome: string): string;
15
+ export declare function readDesktopIntegrationRecord(tuttiHome: string): DesktopIntegrationRecord | null;
16
+ export declare function writeDesktopIntegrationRecord(tuttiHome: string, record: DesktopIntegrationRecord): void;
17
+ //# sourceMappingURL=record.d.ts.map
@@ -0,0 +1,74 @@
1
+ import { chmodSync, closeSync, existsSync, mkdirSync, openSync, readFileSync, renameSync, unlinkSync, writeFileSync, } from "node:fs";
2
+ import { dirname, join } from "node:path";
3
+ const RECORD_KEYS = [
4
+ "schema_version",
5
+ "state",
6
+ "platform",
7
+ "entry_path",
8
+ "protocol",
9
+ "package_version",
10
+ "node_executable",
11
+ "cli_entrypoint",
12
+ "updated_at",
13
+ ];
14
+ function isRecord(value) {
15
+ return typeof value === "object" && value !== null && !Array.isArray(value);
16
+ }
17
+ function isNonEmptyString(value) {
18
+ return typeof value === "string" && value.trim() !== "";
19
+ }
20
+ export function parseDesktopIntegrationRecord(value) {
21
+ if (!isRecord(value) || Object.keys(value).some((key) => !RECORD_KEYS.includes(key))) {
22
+ throw new Error("The desktop integration record is invalid.");
23
+ }
24
+ if (value.schema_version !== 1 ||
25
+ (value.state !== "installed" && value.state !== "removed") ||
26
+ (value.platform !== "darwin" && value.platform !== "linux") ||
27
+ value.protocol !== "tutti" ||
28
+ !isNonEmptyString(value.entry_path) ||
29
+ !isNonEmptyString(value.package_version) ||
30
+ !isNonEmptyString(value.node_executable) ||
31
+ !isNonEmptyString(value.cli_entrypoint) ||
32
+ !isNonEmptyString(value.updated_at) ||
33
+ Number.isNaN(Date.parse(value.updated_at))) {
34
+ throw new Error("The desktop integration record is invalid.");
35
+ }
36
+ return value;
37
+ }
38
+ export function desktopIntegrationRecordPath(tuttiHome) {
39
+ return join(tuttiHome, "desktop", "integration.json");
40
+ }
41
+ export function readDesktopIntegrationRecord(tuttiHome) {
42
+ const path = desktopIntegrationRecordPath(tuttiHome);
43
+ if (!existsSync(path)) {
44
+ return null;
45
+ }
46
+ return parseDesktopIntegrationRecord(JSON.parse(readFileSync(path, "utf8")));
47
+ }
48
+ export function writeDesktopIntegrationRecord(tuttiHome, record) {
49
+ const validated = parseDesktopIntegrationRecord(record);
50
+ const path = desktopIntegrationRecordPath(tuttiHome);
51
+ const directory = dirname(path);
52
+ mkdirSync(directory, { recursive: true, mode: 0o700 });
53
+ chmodSync(directory, 0o700);
54
+ const temporaryPath = join(directory, `.integration-${process.pid}-${Date.now()}.tmp`);
55
+ let descriptor;
56
+ try {
57
+ descriptor = openSync(temporaryPath, "wx", 0o600);
58
+ writeFileSync(descriptor, `${JSON.stringify(validated, null, 2)}\n`, "utf8");
59
+ closeSync(descriptor);
60
+ descriptor = undefined;
61
+ chmodSync(temporaryPath, 0o600);
62
+ renameSync(temporaryPath, path);
63
+ chmodSync(path, 0o600);
64
+ }
65
+ finally {
66
+ if (descriptor !== undefined) {
67
+ closeSync(descriptor);
68
+ }
69
+ if (existsSync(temporaryPath)) {
70
+ unlinkSync(temporaryPath);
71
+ }
72
+ }
73
+ }
74
+ //# sourceMappingURL=record.js.map
@@ -1,14 +1,21 @@
1
1
  export declare const LOCAL_CONSOLE_INVOCATION_ENV_KEYS: readonly ["DBUS_SESSION_BUS_ADDRESS", "DISPLAY", "LANG", "LC_ALL", "LC_CTYPE", "PATH", "TUTTI_HOST_ENDPOINT_HOST", "TUTTI_HOST_ENDPOINT_PORT", "TUTTI_RELAY_URL", "WAYLAND_DISPLAY", "XDG_CURRENT_DESKTOP"];
2
2
  export type LocalConsoleInvocationEnvironment = Partial<Record<(typeof LOCAL_CONSOLE_INVOCATION_ENV_KEYS)[number], string>>;
3
3
  export type LocalConsoleInvocationContext = {
4
+ source: "terminal";
4
5
  currentDirectory: string;
5
6
  environment: LocalConsoleInvocationEnvironment;
7
+ } | {
8
+ source: "desktop";
9
+ environment: LocalConsoleInvocationEnvironment;
6
10
  };
7
11
  export declare function collectLocalConsoleInvocationEnvironment(env: NodeJS.ProcessEnv): LocalConsoleInvocationEnvironment;
8
12
  export declare function createLocalConsoleInvocationContext(options: {
9
13
  cwd: string;
10
14
  env: NodeJS.ProcessEnv;
11
15
  }): LocalConsoleInvocationContext;
16
+ export declare function createDesktopLocalConsoleInvocationContext(options: {
17
+ env: NodeJS.ProcessEnv;
18
+ }): LocalConsoleInvocationContext;
12
19
  export declare function createLocalConsoleServiceEnvironment(options: {
13
20
  env: NodeJS.ProcessEnv;
14
21
  tuttiHome: string;
@@ -24,21 +24,20 @@ export function collectLocalConsoleInvocationEnvironment(env) {
24
24
  }
25
25
  export function createLocalConsoleInvocationContext(options) {
26
26
  return {
27
+ source: "terminal",
27
28
  currentDirectory: resolve(options.cwd),
28
29
  environment: collectLocalConsoleInvocationEnvironment(options.env),
29
30
  };
30
31
  }
32
+ export function createDesktopLocalConsoleInvocationContext(options) {
33
+ return {
34
+ source: "desktop",
35
+ environment: collectLocalConsoleInvocationEnvironment(options.env),
36
+ };
37
+ }
31
38
  export function createLocalConsoleServiceEnvironment(options) {
32
39
  const environment = { TUTTI_HOME: options.tuttiHome };
33
- for (const key of [
34
- "HOME",
35
- "LANG",
36
- "LC_ALL",
37
- "LC_CTYPE",
38
- "LOGNAME",
39
- "TMPDIR",
40
- "USER",
41
- ]) {
40
+ for (const key of ["HOME", "LANG", "LC_ALL", "LC_CTYPE", "LOGNAME", "TMPDIR", "USER"]) {
42
41
  const value = options.env[key];
43
42
  if (value !== undefined && value !== "") {
44
43
  environment[key] = value;
@@ -24,6 +24,7 @@ export declare function ensureLocalConsole(options?: {
24
24
  cwd?: string;
25
25
  env?: NodeJS.ProcessEnv;
26
26
  fetchImpl?: FetchLike;
27
+ source?: "terminal" | "desktop";
27
28
  }): Promise<{
28
29
  url: string;
29
30
  endpoint: LocalConsoleRuntimeEndpoint;