Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 29 additions & 79 deletions commands/create.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import { blue, bold, green } from "@std/fmt/colors";
import { bold, green } from "@std/fmt/colors";
import { Input } from "@cliffy/prompt/input";
import * as path from "@std/path";
import { Confirm } from "@cliffy/prompt/confirm";
import { delay } from "@std/async/delay";
import { Spinner } from "@std/cli/unstable-spinner";
import { promptToInstallSkills } from "./skills.ts";
import type { CommonCommandOptions } from "./common.ts";

Expand All @@ -16,122 +14,74 @@ glue.webhook.onGet((_event) => {
`;

export async function create(_options: CommonCommandOptions) {
await promptToInstallSkills();

let filename = await Input.prompt({
message: "Enter the filename for the new glue",
default: DEFAULT_FILENAME,
});
const code = TEMPLATE_CONTENT;
filename = await uniquifyPath(filename);
filename = appendFileExtensionIfNotPresent(filename, ".ts");
await Deno.writeTextFile(filename, code);
filename = await uniquifyPath(filename);
await Deno.writeTextFile(filename, code, { createNew: true });
console.log(`${green("✔︎")} Successfully created new glue file: ${bold(filename)}`);

await promptToInstallSkills();

await openInEditorFlow(filename);
console.log();
console.log(`💡 Run locally using ${green("glue dev " + filename)}`);
return filename;
}

interface Editor {
name: string;
command: string;
installPage: string;
macOSDownloadUrl: string;
}

const VSCode: Editor = {
name: "VSCode",
command: "code",
installPage: "https://code.visualstudio.com/download",
macOSDownloadUrl: "https://code.visualstudio.com/download",
};
const Cursor: Editor = {
name: "Cursor",
command: "cursor",
installPage: "https://cursor.com/download",
macOSDownloadUrl: "https://cursor.sh",
};
const defaultEditors = ["cursor", "code", "zed"];

async function openInEditorFlow(filename: string) {
const editor = await detectPreferredAndInstalledEditor();
if (!editor) {
console.log();
console.log("Couldn't detect a preferred IDE installed on your system.");
console.log("You may open the created glue file in any text editor.");
console.log(
"We recommend using an IDE such as Cursor (https://cursor.com/) or\nVisual Studio Code (https://code.visualstudio.com/).",
);
return;
}

console.log();
const openInEditor = await Confirm.prompt({
message: "Open created glue in editor?",
message: `Open created glue in editor? (Detected: ${editor})`,
default: true,
});

if (openInEditor) {
let editor = await detectPreferredAndInstalledEditor();
if (!editor) {
const installCursor = await Confirm.prompt({
message: "No editors found, install Cursor (recommended)?",
default: true,
});
if (!installCursor) {
return;
}
await installEditor(Cursor);
editor = Cursor;
}
await openEditor(editor, filename);
}
}

async function detectPreferredAndInstalledEditor(): Promise<Editor | undefined> {
async function detectPreferredAndInstalledEditor(): Promise<string | undefined> {
const editorEnv = Deno.env.get("EDITOR");
const firstTerm = editorEnv?.split(/\s+/)[0];
if (firstTerm === VSCode.command) {
return VSCode;
}
if (firstTerm === Cursor.command) {
return Cursor;
if (firstTerm && defaultEditors.includes(firstTerm)) {
return firstTerm;
}
Comment thread
Macil marked this conversation as resolved.

if (await isEditorInstalled(Cursor)) {
return Cursor;
}
if (await isEditorInstalled(VSCode)) {
return VSCode;
for (const cmd of defaultEditors) {
if (await isEditorInstalled(cmd)) {
return cmd;
}
}
return undefined;
}

async function isEditorInstalled(editor: Editor): Promise<boolean> {
async function isEditorInstalled(editorCommand: string): Promise<boolean> {
try {
await new Deno.Command(editor.command, { args: ["--version"] }).output();
await new Deno.Command(editorCommand, { args: ["--version"] }).output();
return true;
} catch (_error) {
return false;
}
}

async function installEditor(editor: Editor): Promise<void> {
if (Deno.build.os == "darwin") {
// TODO in the future we may install for the user
console.log(`Download and ${editor.name} install from: ${blue(bold(editor.installPage))}`);
} else {
console.log(`Download and ${editor.name} install from: ${blue(bold(editor.installPage))}`);
}

const spinner = new Spinner({
message: `Waiting for ${editor.name} to be installed...`,
});
spinner.start();

while (true) {
try {
await new Deno.Command(editor.command, { args: ["--version"] }).output();
break;
} catch (_e) {
await delay(1000);
}
}
spinner.stop();
}

async function openEditor(editor: Editor, filename: string): Promise<void> {
await new Deno.Command(editor.command, { args: [filename] }).output();
async function openEditor(editorCommand: string, filename: string): Promise<void> {
await new Deno.Command(editorCommand, { args: [filename] }).output();
}

function appendFileExtensionIfNotPresent(filename: string, extension: string): string {
Expand Down
5 changes: 4 additions & 1 deletion commands/login.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,5 +39,8 @@ export const login = async () => {
await open(loginUrl);

await server.finished;
console.log(`\n💡 Run ${mod.green("glue create")} to create your first glue`);
console.log(`\n💡 Run ${mod.green("glue create")} to create your first glue.`);
console.log(
`💡 Run ${mod.green("glue install-skills")} to teach your local coding agents how to use Glue.`,
);
};
8 changes: 8 additions & 0 deletions commands/skills.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,10 @@ export async function installSkills(): Promise<void> {
);
}

/**
* Called at the end of `glue create` to offer to install the Glue skill if we
* detect a supported agent is installed.
*/
export async function promptToInstallSkills(): Promise<void> {
const home = requireHomeDirectory();

Expand All @@ -87,6 +91,10 @@ export async function promptToInstallSkills(): Promise<void> {
return;
}

console.log();
console.log(
"We noticed you have Codex or Claude Code installed but don't have the Glue skill installed for them.",
);
const shouldInstall = await Confirm.prompt({
message: "Install the Glue agent skill for Codex or Claude Code now?",
default: true,
Expand Down