{"_id":"@aicalas/loopy","_rev":"3-a80d7723d03b9aef91573bec251c08fa","name":"@aicalas/loopy","dist-tags":{"latest":"0.2.1"},"versions":{"0.1.0":{"name":"@aicalas/loopy","version":"0.1.0","keywords":["agent","automation","github","temporal","workflow"],"license":"MIT","_id":"@aicalas/loopy@0.1.0","maintainers":[{"name":"aicalas","email":"shyam.sadeesh@gmail.com"}],"homepage":"https://github.com/shyam-sadeesh/loopy#readme","bugs":{"url":"https://github.com/shyam-sadeesh/loopy/issues"},"bin":{"loopy":"dist/cli.js"},"dist":{"shasum":"32f1871abfa1f0001e2876c868e7915183647611","tarball":"https://registry.npmjs.org/@aicalas/loopy/-/loopy-0.1.0.tgz","fileCount":244,"integrity":"sha512-r48tkpM5Xk2QL6x+u6e2fnLlOTArdVVxGIz/BYzSxqvemj0DyHCWOvL2ldczKU4COY2NUWU+u22SBurzlMJHtw==","signatures":[{"sig":"MEUCIBQ9Kc5z6Wn2gz8gQsWPfGY/owFngB83P+45cV5w53q2AiEA3NFt9RTvjkapznfw7p+9NVQbWAiPq3DRLomMjh0aQIc=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":2375060},"type":"module","engines":{"node":">=24 <25","pnpm":">=11 <12"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./workflow":{"types":"./dist/authoring/index.d.ts","import":"./dist/authoring/index.js"}},"gitHead":"1ed3efe88462cee2b501ff069d061494f5a629f5","scripts":{"fix":"biome check --write .","lint":"biome lint .","test":"vitest run","build":"tsc -p tsconfig.build.json","loopy":"node dist/cli.js","format":"biome format --write .","prepare":"pnpm run build","typecheck":"tsc --noEmit","format:check":"biome format .","test:coverage":"vitest run --coverage && tsx scripts/check-coverage.ts"},"_npmUser":{"name":"aicalas","email":"shyam.sadeesh@gmail.com"},"repository":{"url":"git+https://github.com/shyam-sadeesh/loopy.git","type":"git"},"_npmVersion":"11.16.0","description":"Temporal-backed agentic workflow CLI","directories":{},"_nodeVersion":"24.18.0","dependencies":{"ajv":"8.20.0","zod":"4.4.3","fs-ext":"2.1.1","octokit":"5.0.5","commander":"15.0.0","smol-toml":"1.7.0","simple-git":"3.36.0","@opencode-ai/sdk":"1.18.3","@openai/codex-sdk":"0.144.6","@temporalio/client":"1.20.3","@temporalio/common":"1.20.3","@temporalio/worker":"1.20.3","@temporalio/activity":"1.20.3","@temporalio/workflow":"1.20.3","@anthropic-ai/claude-agent-sdk":"0.3.215"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"packageManager":"pnpm@11.15.0","devDependencies":{"tsx":"4.23.1","vitest":"4.1.10","typescript":"7.0.2","@types/node":"24.13.3","@types/fs-ext":"2.0.3","@biomejs/biome":"2.5.4","istanbul-reports":"3.2.0","@temporalio/testing":"1.20.3","istanbul-lib-report":"3.0.1","istanbul-lib-coverage":"3.2.2","@types/istanbul-reports":"3.0.4","@vitest/coverage-istanbul":"4.1.10","@types/istanbul-lib-report":"3.0.3","@types/istanbul-lib-coverage":"2.0.6","@temporalio/nyc-test-coverage":"1.20.3"},"_npmOperationalInternal":{"tmp":"tmp/loopy_0.1.0_1785334873639_0.9707789395876998","host":"s3://npm-registry-packages-npm-production"}},"0.2.0":{"name":"@aicalas/loopy","version":"0.2.0","keywords":["agent","automation","github","temporal","workflow"],"license":"MIT","_id":"@aicalas/loopy@0.2.0","maintainers":[{"name":"aicalas","email":"shyam.sadeesh@gmail.com"}],"homepage":"https://github.com/shyam-sadeesh/loopy#readme","bugs":{"url":"https://github.com/shyam-sadeesh/loopy/issues"},"bin":{"loopy":"dist/cli.js"},"dist":{"shasum":"82d2a84e856e80cd6b90151251ca79726ac45db0","tarball":"https://registry.npmjs.org/@aicalas/loopy/-/loopy-0.2.0.tgz","fileCount":244,"integrity":"sha512-SXH42gZ58eWEXNQPzanm+XauBlBDEO2w6aoLqyGeL1+R8CotRYy4j8hfopKP5L/PiSXb5iBMRMtLK5VppaNqKQ==","signatures":[{"sig":"MEUCIQC45kgQeCWATVphm/f12uAeLjbeGx8eNGWDXn9lgWy95wIgbV9jJ2Df0h59Xr4bS5fhljh+H5RZAHRtXmV/7QraKY8=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":2380632},"type":"module","engines":{"node":">=24 <25","pnpm":">=11 <12"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./workflow":{"types":"./dist/authoring/index.d.ts","import":"./dist/authoring/index.js"}},"gitHead":"b010e5c1c22d2bb48750057d5fe5a20090ed3338","scripts":{"fix":"biome check --write .","lint":"biome lint .","test":"vitest run","build":"tsc -p tsconfig.build.json","loopy":"node dist/cli.js","format":"biome format --write .","prepare":"pnpm run build","typecheck":"tsc --noEmit","format:check":"biome format .","test:coverage":"vitest run --coverage && tsx scripts/check-coverage.ts"},"_npmUser":{"name":"aicalas","email":"shyam.sadeesh@gmail.com"},"repository":{"url":"git+https://github.com/shyam-sadeesh/loopy.git","type":"git"},"_npmVersion":"11.16.0","description":"Temporal-backed agentic workflow CLI","directories":{},"_nodeVersion":"24.18.0","dependencies":{"ajv":"8.20.0","zod":"4.4.3","fs-ext":"2.1.1","octokit":"5.0.5","commander":"15.0.0","smol-toml":"1.7.0","simple-git":"3.36.0","@opencode-ai/sdk":"1.18.3","@openai/codex-sdk":"0.144.6","@temporalio/client":"1.20.3","@temporalio/common":"1.20.3","@temporalio/worker":"1.20.3","@temporalio/activity":"1.20.3","@temporalio/workflow":"1.20.3","@anthropic-ai/claude-agent-sdk":"0.3.215"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"packageManager":"pnpm@11.15.0","devDependencies":{"tsx":"4.23.1","vitest":"4.1.10","typescript":"7.0.2","@types/node":"24.13.3","@types/fs-ext":"2.0.3","@biomejs/biome":"2.5.4","istanbul-reports":"3.2.0","@temporalio/testing":"1.20.3","istanbul-lib-report":"3.0.1","istanbul-lib-coverage":"3.2.2","@types/istanbul-reports":"3.0.4","@vitest/coverage-istanbul":"4.1.10","@types/istanbul-lib-report":"3.0.3","@types/istanbul-lib-coverage":"2.0.6","@temporalio/nyc-test-coverage":"1.20.3"},"_npmOperationalInternal":{"tmp":"tmp/loopy_0.2.0_1785349371547_0.6023021194842333","host":"s3://npm-registry-packages-npm-production"}},"0.2.1":{"name":"@aicalas/loopy","version":"0.2.1","description":"Temporal-backed agentic workflow CLI","type":"module","license":"MIT","repository":{"type":"git","url":"git+https://github.com/shyam-sadeesh/loopy.git"},"homepage":"https://github.com/shyam-sadeesh/loopy#readme","bugs":{"url":"https://github.com/shyam-sadeesh/loopy/issues"},"keywords":["agent","automation","github","temporal","workflow"],"engines":{"node":">=24 <25","pnpm":">=11 <12"},"packageManager":"pnpm@11.15.0","bin":{"loopy":"dist/cli.js"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./workflow":{"types":"./dist/authoring/index.d.ts","import":"./dist/authoring/index.js"}},"publishConfig":{"access":"public"},"scripts":{"build":"tsc -p tsconfig.build.json","fix":"biome check --write .","format":"biome format --write .","format:check":"biome format .","lint":"biome lint .","loopy":"node dist/cli.js","prepare":"pnpm run build","test":"vitest run","test:coverage":"vitest run --coverage && tsx scripts/check-coverage.ts","typecheck":"tsc --noEmit"},"dependencies":{"@anthropic-ai/claude-agent-sdk":"0.3.215","@opencode-ai/sdk":"1.18.3","@openai/codex-sdk":"0.144.6","@temporalio/activity":"1.20.3","@temporalio/client":"1.20.3","@temporalio/common":"1.20.3","@temporalio/worker":"1.20.3","@temporalio/workflow":"1.20.3","ajv":"8.20.0","commander":"15.0.0","fs-ext":"2.1.1","octokit":"5.0.5","simple-git":"3.36.0","smol-toml":"1.7.0","zod":"4.4.3"},"devDependencies":{"@biomejs/biome":"2.5.4","@temporalio/nyc-test-coverage":"1.20.3","@temporalio/testing":"1.20.3","@types/fs-ext":"2.0.3","@types/istanbul-lib-coverage":"2.0.6","@types/istanbul-lib-report":"3.0.3","@types/istanbul-reports":"3.0.4","@types/node":"24.13.3","@vitest/coverage-istanbul":"4.1.10","istanbul-lib-coverage":"3.2.2","istanbul-lib-report":"3.0.1","istanbul-reports":"3.2.0","tsx":"4.23.1","typescript":"7.0.2","vitest":"4.1.10"},"gitHead":"f7c0e2e87acda0670be6dbe288e11b6efeb408b5","_id":"@aicalas/loopy@0.2.1","_nodeVersion":"24.18.0","_npmVersion":"11.16.0","dist":{"integrity":"sha512-Qe64Y7cn09U0Y6FiHq1j6yEm9rLPFp9M1C//QGeVswGNcn/rQ5joMWSQ5nmHXzG9b2DtEMWI9AWZ5LHE63GJSg==","shasum":"15cdc89f5888206a053ca273f9f67181db52b0e3","tarball":"https://registry.npmjs.org/@aicalas/loopy/-/loopy-0.2.1.tgz","fileCount":260,"unpackedSize":2433335,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQD45+TKOSu6T6HZFYbXZv55gxssJGRnehkFhoN/gwynTwIgB5RB4Zh4lXcP8Xmmqv8uTSu5bbBiTRz8uHoxFqX2Vb8="}]},"_npmUser":{"name":"aicalas","email":"shyam.sadeesh@gmail.com"},"directories":{},"maintainers":[{"name":"aicalas","email":"shyam.sadeesh@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/loopy_0.2.1_1785594352981_0.2107280429032914"},"_hasShrinkwrap":false}},"time":{"created":"2026-07-29T14:21:13.464Z","modified":"2026-08-01T14:25:53.357Z","0.1.0":"2026-07-29T14:21:13.836Z","0.2.0":"2026-07-29T18:22:51.677Z","0.2.1":"2026-08-01T14:25:53.203Z"},"bugs":{"url":"https://github.com/shyam-sadeesh/loopy/issues"},"license":"MIT","homepage":"https://github.com/shyam-sadeesh/loopy#readme","keywords":["agent","automation","github","temporal","workflow"],"repository":{"type":"git","url":"git+https://github.com/shyam-sadeesh/loopy.git"},"description":"Temporal-backed agentic workflow CLI","maintainers":[{"name":"aicalas","email":"shyam.sadeesh@gmail.com"}],"readme":"# loopy\n\nLoopy runs durable local agent workflows on Temporal. You author each workflow as a TypeScript function; Loopy discovers it, receives external or scheduled input, provisions isolated git worktrees, runs coding agents, validates structured output, and records orchestration state in Temporal.\n\nLoopy is built for one developer running a local worker against a Temporal Service. Temporal owns workflow state, retries, schedules, histories, and generic execution operations. Loopy owns the workflow authoring API, agent sessions and worktrees, idempotent effects, human questions, and recovery of parked agent calls.\n\nLoopy currently only supports receiving signals from GH. The long term goal is to provide a plugin system for users to build their own integrations.\n\n## Install\n\nLoopy requires Node.js 24 and git.\n\n```bash\nnpm install --global @aicalas/loopy\n```\n\nAdd Loopy and a Standard JSON Schema validator such as Zod to the project that contains your workflow modules:\n\n```bash\nnpm install @aicalas/loopy zod\n```\n\n## Start\n\nThe complete setup, configuration, authoring, storage, and recovery guide ships with the package and works offline without configuration or a Temporal connection:\n\n```bash\nloopy guide\nloopy guide setup\nloopy guide workflows\nloopy guide recovery\n```\n\nAfter configuring the repository, workflow directory, working directory, and Temporal connection, start the local runtime:\n\n```bash\nloopy loop\n```\n\n## Typical Docker Compose setup\n\nA typical local deployment runs the Temporal development server and Loopy as separate Compose services. This single-developer setup uses Temporal's embedded development server; deploy Temporal separately for production.\n\nCreate `config.toml` beside your `workflows/` and `prompts/` directories:\n\n```toml\nrepository = \"owner/repo\"\ndefault_branch = \"main\"\nproject_directory = \"/opt/loopy\"\nworkflows = \"workflows\"\nworking_directory = \"/var/lib/loopy\"\npoll_interval_seconds = 60\n\n[temporal]\ncontrol_task_queue = \"loopy-control\"\nagent_task_queue = \"loopy-agent\"\n\n[agent_worker]\nmax_concurrent_activities = 2\n```\n\nBuild Loopy into the same development environment that its agents need. Workflow modules are imported under Node before they are bundled, so the image installs the workflow project's locked dependencies under `/opt/loopy` rather than relying on globally installed packages. This minimal `Dockerfile.loopy` includes the three supported provider CLIs, but a real image must also install the target repository's language runtimes, package managers, build tools, test dependencies, MCP server executables, and system libraries:\n\n```dockerfile\nFROM node:24-bookworm\n\nRUN apt-get update \\\n    && apt-get install -y --no-install-recommends ca-certificates gh git make openssh-client python3 g++ \\\n    && rm -rf /var/lib/apt/lists/*\nRUN npm install --global @anthropic-ai/claude-code @openai/codex opencode-ai@^1.18.3\nRUN mkdir -p /opt/loopy /var/lib/loopy && chown -R node:node /opt/loopy /var/lib/loopy\n\nWORKDIR /opt/loopy\nCOPY --chown=node:node package.json package-lock.json ./\nUSER node\nRUN npm ci --omit=dev\nENTRYPOINT [\"/opt/loopy/node_modules/.bin/loopy\"]\nCMD [\"--config\", \"/opt/loopy/config.toml\", \"loop\"]\n```\n\nUse the official [Temporal CLI image](https://hub.docker.com/r/temporalio/temporal) for a persistent local development service:\n\n```yaml\nservices:\n  temporal:\n    image: temporalio/temporal:1.8.1\n    command:\n      - server\n      - start-dev\n      - --ip\n      - 0.0.0.0\n      - --db-filename\n      - /home/temporal/temporal.db\n    ports:\n      - \"7233:7233\"\n      - \"8233:8233\"\n    volumes:\n      - temporal-data:/home/temporal\n    restart: unless-stopped\n\n  loopy:\n    build:\n      context: .\n      dockerfile: Dockerfile.loopy\n    depends_on:\n      - temporal\n    env_file:\n      - .env\n    environment:\n      TEMPORAL_ADDRESS: temporal:7233\n    init: true\n    volumes:\n      - ./config.toml:/opt/loopy/config.toml:ro\n      - ./workflows:/opt/loopy/workflows:ro\n      - ./prompts:/opt/loopy/prompts:ro\n      - loopy-work:/var/lib/loopy\n      - agent-home:/home/node\n    restart: unless-stopped\n\nvolumes:\n  temporal-data:\n  loopy-work:\n  agent-home:\n```\n\nThe three volumes provide durability: `temporal-data` holds Workflow histories and schedules, `loopy-work` holds the managed clone and open worktrees, and `agent-home` holds provider authentication, configuration, MCP definitions, and session transcripts.\n\n## Credentials and agent environment\n\nLoopy does not create, broker, or store credentials. Supply only the credentials needed by the workflows and harnesses you use. For a local Compose deployment, put secret environment variables in an uncommitted `.env` file or inject them with your secret manager:\n\n```dotenv\nGITHUB_TOKEN=github-token\nANTHROPIC_API_KEY=anthropic-key\nCODEX_API_KEY=openai-key\n# Add the provider-specific variables referenced by your OpenCode configuration.\n```\n\nStart the stack and open Temporal UI at `http://localhost:8233`:\n\n```bash\ndocker compose up --build -d\ndocker compose logs -f loopy\n```\n\n## Functionality\n\n| Capability | Built-in behavior |\n| --- | --- |\n| Intake | Poll labelled GitHub issues or pull requests, or start from a Temporal cron/interval schedule. Edge-mode watchers consume the intake label before user code runs; level mode repeats while it remains attached. |\n| Agents | Run Claude, Codex, or OpenCode in isolated git worktrees and resume the same provider session across calls. |\n| Control flow | Use ordinary TypeScript branches, loops, sequential calls, and `Promise.all` fan-out inside the Temporal Workflow. |\n| Results | Require every agent call to return a value validated by a Standard JSON Schema validator or literal JSON Schema. |\n| Effects | Apply idempotent GitHub label changes once per effect ID. |\n| Human input | Post a GitHub question, park without holding an agent slot, and resume from the reply or `loopy answer`. |\n| Prompts and progress | Read prompt files with explicit scalar interpolation and publish the latest progress to Temporal Current Details. |\n| Recovery | Drain safely on shutdown and reconcile or retry parked agent calls with `loopy resolve`. |\n\n## Workflow example\n\nEach TypeScript module default-exports a workflow created with `defineWorkflow` from `@aicalas/loopy/workflow`. This example covers structured agent output, a prompt file, progress, human input, and an idempotent effect:\n\n```ts\nimport {\n  claude,\n  defineWorkflow,\n  githubAttention,\n  githubComments,\n  githubLabels,\n  githubPullRequests,\n  promptFile,\n  triggerIssue,\n} from \"@aicalas/loopy/workflow\";\nimport { z } from \"zod\";\n\nconst Review = z.object({\n  summary: z.string(),\n  question: z.string().nullable(),\n});\n\nexport default defineWorkflow({\n  name: \"review\",\n  watcher: githubPullRequests({\n    repository: \"owner/repo\",\n    label: \"agent:review\",\n    onStart: { add: [\"agent:running\"] },\n  }),\n  attention: githubAttention({ target: \"trigger\" }),\n  async run(ctx) {\n    const issue = triggerIssue(ctx.trigger);\n    const reviewer = ctx.agent(\n      \"reviewer\",\n      claude({ model: \"claude-opus-4-8\", effort: \"high\", permissionMode: \"acceptEdits\" }),\n    );\n    ctx.progress(\"Reviewing the pull request\");\n    let review = await reviewer.run({\n      output: Review,\n      prompt: [promptFile(\"prompts/review.md\", { repository: ctx.trigger.repository, issue })],\n    });\n    if (review.question !== null) {\n      const answer = await ctx.ask(\n        \"review-question\",\n        githubComments({ repository: ctx.trigger.repository, issue }),\n        { prompt: [review.question] },\n      );\n      review = await reviewer.run({\n        output: Review,\n        prompt: [`The human answered: ${answer}`],\n      });\n    }\n    await ctx.effect(\n      \"mark-reviewed\",\n      githubLabels({\n        repository: ctx.trigger.repository,\n        issue,\n        add: [\"agent:reviewed\"],\n        remove: [\"agent:running\"],\n      }),\n    );\n    return review;\n  },\n});\n```\n\nThe GitHub watcher claims each matching pull request, then Temporal runs the workflow. The named agent keeps its provider session and worktree across calls.\n\n## Scheduled workflows\n\nA workflow declares one input. Use a Temporal schedule instead of a watcher for time-based work:\n\n```ts\nimport { defineWorkflow } from \"@aicalas/loopy/workflow\";\n\nexport default defineWorkflow({\n  name: \"nightly-maintenance\",\n  schedule: { cron: \"0 2 * * *\", timezone: \"UTC\" },\n  async run(ctx) {\n    return { source: ctx.trigger.source };\n  },\n});\n```\n\nSchedules also accept `intervalSeconds` and an overlap policy. Attention is optional for scheduled workflows; GitHub attention must target a fixed issue because there is no source issue or pull request.\n\n## Agent harnesses\n\n| Factory | Provider | Required options | Session behavior |\n| --- | --- | --- | --- |\n| `claude` | Anthropic Claude Agent SDK | `model`, `effort` | Resumes a Claude session in the same worktree path. |\n| `codex` | OpenAI Codex SDK | `model`, `effort` | Resumes a Codex thread in the same worktree. |\n| `opencode` | OpenCode SDK v2 and OpenCode 1.18.3 or newer on `PATH` | `model` as `provider/model`, `effort` | Resumes an OpenCode session in the same worktree. |\n\nHarness options also control provider isolation, permissions, approvals, and filesystem sandboxing. See `loopy guide workflows` for the authoring boundaries and remember that provider configuration and repository instructions can grant tools and credentials beyond Loopy's declared adapters.\n\n## Adapters\n\nImport adapter factories from `@aicalas/loopy/workflow`. Each adapter declares its own repository, target, and credential source.\n\n| Kind | Factories | Role |\n| --- | --- | --- |\n| Watcher | `githubIssues`, `githubPullRequests` | Poll a repository for a label and claim each match. |\n| Harness | `claude`, `codex`, `opencode` | Run the agent behind `ctx.agent(id, harness)`. |\n| Attention | `githubAttention` | Report halted, parked, stuck, and awaiting-answer workflows. |\n| Effect | `githubLabels` | Apply idempotent writes through `ctx.effect(id, effect)`. |\n| Channel | `githubComments` | Ask a human through `ctx.ask(id, channel, request)`. |\n| Prompt | `promptFile` | Read a prompt segment from disk outside the Workflow isolate. |\n\nThe GitHub adapters make label changes idempotent by converging on the desired label set. Before changing an issue or pull request, Loopy reads its current labels, applies the requested additions and removals, and writes the result only if it changed. Retrying the operation reaches the same state: adding a label that is already present or removing one that is already absent does nothing.\n\nAgent `output` accepts a Standard JSON Schema validator such as Zod, Valibot, or ArkType, or a literal JSON Schema.\n\nPrompt files interpolate explicitly supplied scalar values. Use named placeholders such as `{{ repository }}` in the file. Names must be identifiers; strings are inserted verbatim, while finite numbers, booleans, and `null` use their normal JSON scalar text. Every placeholder needs a supplied value, unused values are allowed, and malformed placeholders or unsupported values halt the workflow before Loopy contacts an agent or posts a question.\n\nPreserve the Temporal Service state, Loopy config, configured working directory, and each agent provider's session data while executions are open. A session ID stored in Temporal cannot reconstruct a deleted provider transcript or worktree.\n\nUse Temporal UI or CLI for execution inspection, Event History, cancellation, reset, termination, and Schedule administration. Use `loopy resolve` for a parked agent call and `loopy answer` for a workflow waiting on human input.\n","readmeFilename":"README.md"}