branch:
prompt.ts
12020 bytesRaw
/**
 * TypeScript declaration for the `state` object injected into every isolate
 * execution. Export this string in prompts so the LLM knows the exact API it
 * can call.
 *
 * Usage:
 *   import { STATE_TYPES, STATE_SYSTEM_PROMPT } from "@cloudflare/shell";
 *
 *   system: STATE_SYSTEM_PROMPT.replace("{{types}}", STATE_TYPES)
 */
export const STATE_TYPES = `
// ── Primitive types ───────────────────────────────────────────────────────
type StateEntryType = "file" | "directory" | "symlink";

type StateStat = {
  type: StateEntryType;
  size: number;
  mtime: Date;
  mode?: number;
};

type StateDirent = {
  name: string;
  type: StateEntryType;
};

// ── Options ───────────────────────────────────────────────────────────────
type StateMkdirOptions   = { recursive?: boolean };
type StateRmOptions      = { recursive?: boolean; force?: boolean };
type StateCopyOptions    = { recursive?: boolean };
type StateMoveOptions    = { recursive?: boolean };
type StateTreeOptions    = { maxDepth?: number };
type StateJsonWriteOptions = { spaces?: number };
type StateHashOptions    = { algorithm?: "md5" | "sha1" | "sha256" };

type StateSearchOptions = {
  caseSensitive?: boolean;
  regex?: boolean;
  wholeWord?: boolean;
  contextBefore?: number;
  contextAfter?: number;
  maxMatches?: number;
};

type StateReplaceInFilesOptions = StateSearchOptions & {
  dryRun?: boolean;
  rollbackOnError?: boolean;
};

type StateApplyEditsOptions = {
  dryRun?: boolean;
  rollbackOnError?: boolean;
};

type StateFindOptions = {
  name?: string;
  pathPattern?: string;
  type?: StateEntryType | StateEntryType[];
  minDepth?: number;
  maxDepth?: number;
  empty?: boolean;
  sizeMin?: number;
  sizeMax?: number;
  mtimeAfter?: string | Date;
  mtimeBefore?: string | Date;
};

// ── Result types ──────────────────────────────────────────────────────────
type StateTextMatch = {
  line: number;
  column: number;
  match: string;
  lineText: string;
  beforeLines?: string[];
  afterLines?: string[];
};

type StateFindEntry = {
  path: string;
  name: string;
  type: StateEntryType;
  depth: number;
  size: number;
  mtime: Date;
};

type StateTreeNode = {
  path: string;
  name: string;
  type: StateEntryType;
  size: number;
  children?: StateTreeNode[];
};

type StateTreeSummary = {
  files: number;
  directories: number;
  symlinks: number;
  totalBytes: number;
  maxDepth: number;
};

type StateFileDetection = {
  mime: string;
  description: string;
  extension?: string;
  binary: boolean;
};

type StateFileSearchResult = {
  path: string;
  matches: StateTextMatch[];
};

type StateReplaceResult = { replaced: number; content: string };

type StateFileReplaceResult = {
  path: string;
  replaced: number;
  content: string;
  diff: string;
};

type StateReplaceInFilesResult = {
  dryRun: boolean;
  files: StateFileReplaceResult[];
  totalFiles: number;
  totalReplacements: number;
};

type StateJsonUpdateOperation =
  | { op: "set"; path: string; value: unknown }
  | { op: "delete"; path: string };

type StateJsonUpdateResult = {
  value: unknown;
  content: string;
  diff: string;
  operationsApplied: number;
};

type StateArchiveEntry = { path: string; type: "file" | "directory"; size: number };

type StateArchiveCreateResult = {
  path: string;
  entries: StateArchiveEntry[];
  bytesWritten: number;
};

type StateArchiveExtractResult = {
  destination: string;
  entries: StateArchiveEntry[];
};

type StateCompressionResult = {
  path: string;
  destination: string;
  bytesWritten: number;
};

// ── Edit planning ─────────────────────────────────────────────────────────
type StateEdit = { path: string; content: string };

type StateEditInstruction =
  | { kind: "write";     path: string; content: string }
  | { kind: "replace";   path: string; search: string; replacement: string; options?: StateSearchOptions }
  | { kind: "writeJson"; path: string; value: unknown; options?: StateJsonWriteOptions };

type StatePlannedEdit = {
  instruction: StateEditInstruction;
  path: string;
  changed: boolean;
  content: string;
  diff: string;
};

type StateEditPlan = {
  edits: StatePlannedEdit[];
  totalChanged: number;
  totalInstructions: number;
};

type StateAppliedEditResult = {
  path: string;
  changed: boolean;
  content: string;
  diff: string;
};

type StateApplyEditsResult = {
  dryRun: boolean;
  edits: StateAppliedEditResult[];
  totalChanged: number;
};

// ── state object ──────────────────────────────────────────────────────────
declare const state: {
  // File I/O
  /** Read a file as text. */
  readFile(path: string): Promise<string>;
  /** Read a file as bytes. */
  readFileBytes(path: string): Promise<Uint8Array>;
  /** Write text to a file, creating parent directories as needed. */
  writeFile(path: string, content: string): Promise<void>;
  /** Write bytes to a file. */
  writeFileBytes(path: string, content: Uint8Array): Promise<void>;
  /** Append text or bytes to a file. */
  appendFile(path: string, content: string | Uint8Array): Promise<void>;

  // JSON
  /** Parse a JSON file and return the value. */
  readJson(path: string): Promise<unknown>;
  /** Write a value as JSON to a file. */
  writeJson(path: string, value: unknown, options?: StateJsonWriteOptions): Promise<void>;
  /** Query a JSON file using dot-path syntax like ".key[0].nested". */
  queryJson(path: string, query: string): Promise<unknown>;
  /** Apply set/delete operations to a JSON file in place. */
  updateJson(path: string, operations: StateJsonUpdateOperation[]): Promise<StateJsonUpdateResult>;

  // Metadata & directories
  /** Return true if the path exists. */
  exists(path: string): Promise<boolean>;
  /** Stat a path, following symlinks. Returns null if not found. */
  stat(path: string): Promise<StateStat | null>;
  /** Stat a path without following symlinks. Returns null if not found. */
  lstat(path: string): Promise<StateStat | null>;
  /** Create a directory. */
  mkdir(path: string, options?: StateMkdirOptions): Promise<void>;
  /** List names in a directory. */
  readdir(path: string): Promise<string[]>;
  /** List directory entries with type information. */
  readdirWithFileTypes(path: string): Promise<StateDirent[]>;

  // Tree traversal
  /** Find files/directories matching structured predicates. */
  find(path: string, options?: StateFindOptions): Promise<StateFindEntry[]>;
  /** Recursively build the directory tree. */
  walkTree(path: string, options?: StateTreeOptions): Promise<StateTreeNode>;
  /** Summarize file counts and sizes in a subtree. */
  summarizeTree(path: string, options?: StateTreeOptions): Promise<StateTreeSummary>;

  // Search & replace
  /** Search for matches in a single file. */
  searchText(path: string, query: string, options?: StateSearchOptions): Promise<StateTextMatch[]>;
  /** Search for matches across files matching a glob pattern. */
  searchFiles(pattern: string, query: string, options?: StateSearchOptions): Promise<StateFileSearchResult[]>;
  /** Replace matches in a single file. */
  replaceInFile(path: string, search: string, replacement: string, options?: StateSearchOptions): Promise<StateReplaceResult>;
  /** Replace matches across all files matching a glob. Transactional by default. */
  replaceInFiles(pattern: string, search: string, replacement: string, options?: StateReplaceInFilesOptions): Promise<StateReplaceInFilesResult>;

  // File operations
  /** Remove a file or directory. */
  rm(path: string, options?: StateRmOptions): Promise<void>;
  /** Copy a file or directory. */
  cp(src: string, dest: string, options?: StateCopyOptions): Promise<void>;
  /** Move a file or directory. */
  mv(src: string, dest: string, options?: StateMoveOptions): Promise<void>;
  /** Create a symlink. */
  symlink(target: string, linkPath: string): Promise<void>;
  /** Read a symlink target. */
  readlink(path: string): Promise<string>;
  /** Resolve all symlinks to a canonical path. */
  realpath(path: string): Promise<string>;
  /** Resolve a relative path against a base directory. */
  resolvePath(base: string, path: string): Promise<string>;
  /** Find paths matching a glob pattern. */
  glob(pattern: string): Promise<string[]>;
  /** Unified diff between two files. */
  diff(pathA: string, pathB: string): Promise<string>;
  /** Unified diff between a file and new content. */
  diffContent(path: string, newContent: string): Promise<string>;
  /** Recursively remove a directory tree. */
  removeTree(path: string): Promise<void>;
  /** Recursively copy a directory tree. */
  copyTree(src: string, dest: string): Promise<void>;
  /** Recursively move a directory tree. */
  moveTree(src: string, dest: string): Promise<void>;

  // Archives & compression
  /** Pack sources into a tar archive. */
  createArchive(path: string, sources: string[]): Promise<StateArchiveCreateResult>;
  /** List entries in a tar archive. */
  listArchive(path: string): Promise<StateArchiveEntry[]>;
  /** Extract a tar archive to a destination directory. */
  extractArchive(path: string, destination: string): Promise<StateArchiveExtractResult>;
  /** Gzip-compress a file. Default destination is \`path + ".gz"\`. */
  compressFile(path: string, destination?: string): Promise<StateCompressionResult>;
  /** Gunzip a compressed file. */
  decompressFile(path: string, destination?: string): Promise<StateCompressionResult>;
  /** Hash a file and return the hex digest. */
  hashFile(path: string, options?: StateHashOptions): Promise<string>;
  /** Detect the MIME type and binary/text nature of a file. */
  detectFile(path: string): Promise<StateFileDetection>;

  // Structured edit planning
  /**
   * Plan a batch of edits — compute content + diffs without writing.
   * Instructions: { kind: "write" | "replace" | "writeJson", path, ... }
   */
  planEdits(instructions: StateEditInstruction[]): Promise<StateEditPlan>;
  /**
   * Apply a previously computed edit plan to disk.
   * Use dryRun: true to preview without writing.
   */
  applyEditPlan(plan: StateEditPlan, options?: StateApplyEditsOptions): Promise<StateApplyEditsResult>;
  /**
   * Apply a list of raw { path, content } edits.
   * Transactional by default: rolls back earlier writes if any write fails.
   */
  applyEdits(edits: StateEdit[], options?: StateApplyEditsOptions): Promise<StateApplyEditsResult>;
};
`.trim();

/**
 * System-prompt template for using the `state` runtime.
 * Replace `{{types}}` with `STATE_TYPES` before passing to the model.
 *
 * Example:
 *   import { STATE_TYPES, STATE_SYSTEM_PROMPT } from "@cloudflare/shell";
 *   const system = STATE_SYSTEM_PROMPT.replace("{{types}}", STATE_TYPES);
 */
export const STATE_SYSTEM_PROMPT = `
You can write JavaScript code that runs inside an isolated sandbox with access to a persistent
virtual filesystem through the \`state\` object.

Rules:
- Write an async function: \`async () => { ... return result; }\`
- Do NOT use TypeScript syntax — no type annotations, interfaces, or generics in your code.
- Do NOT use \`import\` statements — all helpers are available through \`state\`.
- Always \`return\` the final value you want back.
- For multi-file refactors, prefer \`planEdits()\` + \`applyEditPlan()\` over many individual writes.
- For search-and-replace across a tree, use \`replaceInFiles()\` — it is transactional by default.

Available API (TypeScript reference):

\`\`\`typescript
{{types}}
\`\`\`
`.trim();