@hasna/loops 0.4.0 → 0.4.1

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.
package/dist/cli/index.js CHANGED
@@ -3964,6 +3964,257 @@ class Store {
3964
3964
  this.db.close();
3965
3965
  }
3966
3966
  }
3967
+ // package.json
3968
+ var package_default = {
3969
+ name: "@hasna/loops",
3970
+ version: "0.4.1",
3971
+ description: "Persistent local loop and workflow runner for deterministic commands and headless AI coding agents",
3972
+ type: "module",
3973
+ main: "dist/index.js",
3974
+ types: "dist/index.d.ts",
3975
+ bin: {
3976
+ loops: "dist/cli/index.js",
3977
+ "loops-daemon": "dist/daemon/index.js",
3978
+ "loops-api": "dist/api/index.js",
3979
+ "loops-runner": "dist/runner/index.js",
3980
+ "loops-mcp": "dist/mcp/index.js"
3981
+ },
3982
+ exports: {
3983
+ ".": {
3984
+ types: "./dist/index.d.ts",
3985
+ import: "./dist/index.js"
3986
+ },
3987
+ "./sdk": {
3988
+ types: "./dist/sdk/index.d.ts",
3989
+ import: "./dist/sdk/index.js"
3990
+ },
3991
+ "./mcp": {
3992
+ types: "./dist/mcp/index.d.ts",
3993
+ import: "./dist/mcp/index.js"
3994
+ },
3995
+ "./api": {
3996
+ types: "./dist/api/index.d.ts",
3997
+ import: "./dist/api/index.js"
3998
+ },
3999
+ "./runner": {
4000
+ types: "./dist/runner/index.d.ts",
4001
+ import: "./dist/runner/index.js"
4002
+ },
4003
+ "./mode": {
4004
+ types: "./dist/lib/mode.d.ts",
4005
+ import: "./dist/lib/mode.js"
4006
+ },
4007
+ "./storage": {
4008
+ types: "./dist/lib/store.d.ts",
4009
+ import: "./dist/lib/store.js"
4010
+ }
4011
+ },
4012
+ files: [
4013
+ "dist",
4014
+ "README.md",
4015
+ "CHANGELOG.md",
4016
+ "docs",
4017
+ "LICENSE"
4018
+ ],
4019
+ scripts: {
4020
+ build: "rm -rf dist && bun build src/cli/index.ts src/daemon/index.ts src/api/index.ts src/runner/index.ts src/mcp/index.ts src/index.ts src/sdk/index.ts src/lib/store.ts src/lib/mode.ts --root src --outdir dist --target bun --packages external && chmod +x dist/cli/index.js dist/daemon/index.js dist/api/index.js dist/runner/index.js dist/mcp/index.js && tsc -p tsconfig.build.json --emitDeclarationOnly --outDir dist",
4021
+ "build:bin": "bun build src/cli/index.ts --compile --outfile dist/loops",
4022
+ typecheck: "tsc --noEmit",
4023
+ test: "bun test",
4024
+ "test:boundary": "bun run scripts/no-private-cloud-boundary.mjs",
4025
+ prepare: "test -d dist || bun run build",
4026
+ "dev:cli": "bun run src/cli/index.ts",
4027
+ "dev:daemon": "bun run src/daemon/index.ts",
4028
+ prepublishOnly: "bun run build && bun run test:boundary"
4029
+ },
4030
+ keywords: [
4031
+ "loops",
4032
+ "scheduler",
4033
+ "daemon",
4034
+ "agents",
4035
+ "claude",
4036
+ "cursor",
4037
+ "codewith",
4038
+ "opencode",
4039
+ "cli"
4040
+ ],
4041
+ repository: {
4042
+ type: "git",
4043
+ url: "git+https://github.com/hasna/loops.git"
4044
+ },
4045
+ homepage: "https://github.com/hasna/loops#readme",
4046
+ bugs: {
4047
+ url: "https://github.com/hasna/loops/issues"
4048
+ },
4049
+ author: "Hasna <andrei@hasna.com>",
4050
+ license: "Apache-2.0",
4051
+ engines: {
4052
+ bun: ">=1.0.0"
4053
+ },
4054
+ dependencies: {
4055
+ "@hasna/events": "^0.1.9",
4056
+ "@modelcontextprotocol/sdk": "^1.29.0",
4057
+ "@openrouter/ai-sdk-provider": "2.9.1",
4058
+ ai: "6.0.204",
4059
+ commander: "^13.1.0",
4060
+ zod: "4.4.3"
4061
+ },
4062
+ optionalDependencies: {
4063
+ "@hasna/machines": "0.0.49"
4064
+ },
4065
+ devDependencies: {
4066
+ "@types/bun": "latest",
4067
+ typescript: "^5.7.3"
4068
+ },
4069
+ publishConfig: {
4070
+ registry: "https://registry.npmjs.org",
4071
+ access: "public"
4072
+ }
4073
+ };
4074
+
4075
+ // src/lib/version.ts
4076
+ function packageVersion() {
4077
+ if (typeof package_default.version !== "string" || package_default.version.trim() === "")
4078
+ throw new Error("package.json version is missing");
4079
+ return package_default.version;
4080
+ }
4081
+
4082
+ // src/lib/mode.ts
4083
+ var LOOP_DEPLOYMENT_MODES = ["local", "self_hosted", "cloud"];
4084
+ var MODE_ENV_KEYS = ["LOOPS_MODE", "HASNA_LOOPS_MODE"];
4085
+ var API_URL_ENV_KEYS = ["LOOPS_API_URL", "HASNA_LOOPS_API_URL"];
4086
+ var CLOUD_API_URL_ENV_KEYS = ["LOOPS_CLOUD_API_URL", "HASNA_LOOPS_CLOUD_API_URL"];
4087
+ var DATABASE_URL_ENV_KEYS = ["LOOPS_DATABASE_URL", "HASNA_LOOPS_DATABASE_URL"];
4088
+ var API_TOKEN_ENV_KEYS = ["LOOPS_API_TOKEN", "HASNA_LOOPS_API_TOKEN"];
4089
+ var CLOUD_TOKEN_ENV_KEYS = ["LOOPS_CLOUD_TOKEN", "HASNA_LOOPS_CLOUD_TOKEN"];
4090
+ var TOKEN_ENV_KEYS = [...API_TOKEN_ENV_KEYS, ...CLOUD_TOKEN_ENV_KEYS];
4091
+ function envValue(env, keys) {
4092
+ for (const key of keys) {
4093
+ const value = env[key]?.trim();
4094
+ if (value)
4095
+ return { key, value };
4096
+ }
4097
+ return;
4098
+ }
4099
+ function normalizeLoopDeploymentMode(value) {
4100
+ const normalized = value.trim().toLowerCase().replace(/-/g, "_");
4101
+ if (normalized === "local")
4102
+ return "local";
4103
+ if (normalized === "self_hosted" || normalized === "selfhosted")
4104
+ return "self_hosted";
4105
+ if (normalized === "cloud" || normalized === "saas")
4106
+ return "cloud";
4107
+ throw new Error(`unsupported OpenLoops deployment mode "${value}"; expected local, self_hosted, or cloud`);
4108
+ }
4109
+ function resolveLoopDeploymentMode(env = process.env) {
4110
+ const explicitMode = envValue(env, MODE_ENV_KEYS);
4111
+ if (explicitMode) {
4112
+ return {
4113
+ deploymentMode: normalizeLoopDeploymentMode(explicitMode.value),
4114
+ source: explicitMode.key
4115
+ };
4116
+ }
4117
+ const cloudApiUrl = envValue(env, CLOUD_API_URL_ENV_KEYS);
4118
+ if (cloudApiUrl)
4119
+ return { deploymentMode: "cloud", source: cloudApiUrl.key };
4120
+ const apiUrl = envValue(env, API_URL_ENV_KEYS);
4121
+ if (apiUrl)
4122
+ return { deploymentMode: "self_hosted", source: apiUrl.key };
4123
+ const databaseUrl = envValue(env, DATABASE_URL_ENV_KEYS);
4124
+ if (databaseUrl)
4125
+ return { deploymentMode: "self_hosted", source: databaseUrl.key };
4126
+ return { deploymentMode: "local", source: "default" };
4127
+ }
4128
+ function loopControlPlaneConfig(env = process.env) {
4129
+ return {
4130
+ apiUrl: envValue(env, API_URL_ENV_KEYS)?.value,
4131
+ cloudApiUrl: envValue(env, CLOUD_API_URL_ENV_KEYS)?.value,
4132
+ databaseUrlPresent: Boolean(envValue(env, DATABASE_URL_ENV_KEYS)),
4133
+ apiAuthTokenPresent: Boolean(envValue(env, API_TOKEN_ENV_KEYS)),
4134
+ cloudAuthTokenPresent: Boolean(envValue(env, CLOUD_TOKEN_ENV_KEYS)),
4135
+ authTokenPresent: Boolean(envValue(env, TOKEN_ENV_KEYS))
4136
+ };
4137
+ }
4138
+ function sourceOfTruthForMode(mode) {
4139
+ if (mode === "local")
4140
+ return "local_sqlite";
4141
+ if (mode === "self_hosted")
4142
+ return "self_hosted_control_plane";
4143
+ return "cloud_control_plane";
4144
+ }
4145
+ function displayControlPlaneUrl(value) {
4146
+ if (!value)
4147
+ return;
4148
+ try {
4149
+ const url = new URL(value);
4150
+ const path = url.pathname === "/" ? "" : url.pathname.replace(/\/+$/g, "");
4151
+ return `${url.origin}${path}`;
4152
+ } catch {
4153
+ return "[invalid-url]";
4154
+ }
4155
+ }
4156
+ function buildDeploymentStatus(opts = {}) {
4157
+ const env = opts.env ?? process.env;
4158
+ const active = resolveLoopDeploymentMode(env);
4159
+ const deploymentMode = opts.perspective ?? active.deploymentMode;
4160
+ const config = loopControlPlaneConfig(env);
4161
+ const rawApiUrl = deploymentMode === "cloud" ? config.cloudApiUrl : config.apiUrl;
4162
+ const apiUrl = displayControlPlaneUrl(rawApiUrl);
4163
+ const controlPlaneKind = deploymentMode === "local" ? "none" : deploymentMode;
4164
+ const deploymentAuthTokenPresent = deploymentMode === "cloud" ? config.cloudAuthTokenPresent : deploymentMode === "self_hosted" ? config.apiAuthTokenPresent : false;
4165
+ const controlPlaneConfigured = deploymentMode === "local" ? false : deploymentMode === "self_hosted" ? Boolean(config.apiUrl || config.databaseUrlPresent) : Boolean(config.cloudApiUrl && deploymentAuthTokenPresent);
4166
+ const warnings = [];
4167
+ if (deploymentMode === "self_hosted" && !controlPlaneConfigured) {
4168
+ warnings.push("self_hosted mode needs LOOPS_API_URL or LOOPS_DATABASE_URL before it can become authoritative");
4169
+ }
4170
+ if (deploymentMode === "cloud" && config.apiUrl && !config.cloudApiUrl) {
4171
+ warnings.push("LOOPS_API_URL selects self_hosted; cloud mode uses LOOPS_CLOUD_API_URL");
4172
+ }
4173
+ if (deploymentMode === "cloud" && !config.cloudApiUrl) {
4174
+ warnings.push("cloud mode is a public contract until LOOPS_CLOUD_API_URL is configured");
4175
+ }
4176
+ if (deploymentMode === "cloud" && config.cloudApiUrl && !deploymentAuthTokenPresent) {
4177
+ warnings.push("cloud mode needs LOOPS_CLOUD_TOKEN or HASNA_LOOPS_CLOUD_TOKEN before it can become ready");
4178
+ }
4179
+ if (opts.perspective && opts.perspective !== active.deploymentMode) {
4180
+ warnings.push(`active deployment mode is ${active.deploymentMode}; this is the ${opts.perspective} perspective`);
4181
+ }
4182
+ return {
4183
+ packageVersion: packageVersion(),
4184
+ deploymentMode,
4185
+ activeDeploymentMode: active.deploymentMode,
4186
+ active: deploymentMode === active.deploymentMode,
4187
+ deploymentModeSource: active.source,
4188
+ sourceOfTruth: sourceOfTruthForMode(deploymentMode),
4189
+ localStore: {
4190
+ role: deploymentMode === "local" ? "authoritative" : "cache_and_spool"
4191
+ },
4192
+ controlPlane: {
4193
+ kind: controlPlaneKind,
4194
+ configured: controlPlaneConfigured,
4195
+ apiUrl,
4196
+ databaseUrlPresent: config.databaseUrlPresent,
4197
+ authTokenPresent: deploymentAuthTokenPresent
4198
+ },
4199
+ runner: {
4200
+ required: deploymentMode !== "local",
4201
+ role: deploymentMode === "local" ? "daemon" : "control_plane_worker"
4202
+ },
4203
+ warnings
4204
+ };
4205
+ }
4206
+ function deploymentStatusLine(status) {
4207
+ const configured = status.controlPlane.kind === "none" ? "none" : String(status.controlPlane.configured);
4208
+ const active = status.active ? "active" : `active=${status.activeDeploymentMode}`;
4209
+ return [
4210
+ `deploymentMode=${status.deploymentMode}`,
4211
+ active,
4212
+ `source=${status.deploymentModeSource}`,
4213
+ `truth=${status.sourceOfTruth}`,
4214
+ `local=${status.localStore.role}`,
4215
+ `control_plane=${configured}`
4216
+ ].join(" ");
4217
+ }
3967
4218
 
3968
4219
  // src/cli/index.ts
3969
4220
  import { randomUUID as randomUUID2 } from "crypto";
@@ -7701,6 +7952,7 @@ function runDoctor(store) {
7701
7952
 
7702
7953
  // src/lib/hygiene.ts
7703
7954
  import { basename as basename3 } from "path";
7955
+ import { homedir as homedir3 } from "os";
7704
7956
  var PROVIDER_TOKENS = new Set([
7705
7957
  "codewith",
7706
7958
  "claude",
@@ -7715,11 +7967,14 @@ var PROVIDER_TOKENS = new Set([
7715
7967
  var REPO_GENERIC_TOKENS = new Set(["repo", "repoops"]);
7716
7968
  var CADENCE_SUFFIX_TOKENS = new Set(["hourly", "daily", "weekly", "monthly"]);
7717
7969
  var CADENCE_SUFFIX_PATTERN = /^(?:every-?)?\d+(?:s|m|h|d|w)$/;
7970
+ function userHome() {
7971
+ return process.env.HOME || homedir3();
7972
+ }
7718
7973
  function slugify(value) {
7719
7974
  return value.normalize("NFKD").replace(/[^\w\s.-]/g, "-").replace(/[_\s.:/]+/g, "-").replace(/[^a-zA-Z0-9-]+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").toLowerCase();
7720
7975
  }
7721
7976
  function repoSlugFromCwd(cwd) {
7722
- if (!cwd || cwd === process.env.HOME || cwd === "/home/hasna")
7977
+ if (!cwd || cwd === userHome())
7723
7978
  return "";
7724
7979
  if (cwd.includes("/.hasna/loops/"))
7725
7980
  return "";
@@ -7902,7 +8157,7 @@ function commandText(loop) {
7902
8157
  return [loop.target.command, ...loop.target.args ?? []].join(" ");
7903
8158
  }
7904
8159
  function scriptNeedles(scriptsDir) {
7905
- const home = process.env.HOME ?? "/home/hasna";
8160
+ const home = userHome();
7906
8161
  const normalized = scriptsDir.replace(/\/+$/g, "");
7907
8162
  const values = [
7908
8163
  normalized,
@@ -7920,7 +8175,7 @@ function scriptNeedles(scriptsDir) {
7920
8175
  return [...new Set(values)];
7921
8176
  }
7922
8177
  function buildScriptInventoryReport(store, opts = {}) {
7923
- const scriptsDir = opts.scriptsDir ?? `${process.env.HOME ?? "/home/hasna"}/.hasna/loops/scripts`;
8178
+ const scriptsDir = opts.scriptsDir ?? `${userHome()}/.hasna/loops/scripts`;
7924
8179
  const needles = scriptNeedles(scriptsDir);
7925
8180
  const loops = managedLoops(store, { includeInactive: opts.includeInactive, includeStopped: true, limit: opts.limit });
7926
8181
  const scriptBacked = loops.map((loop) => {
@@ -7947,110 +8202,11 @@ function buildScriptInventoryReport(store, opts = {}) {
7947
8202
  loops: scriptBacked
7948
8203
  };
7949
8204
  }
7950
- // package.json
7951
- var package_default = {
7952
- name: "@hasna/loops",
7953
- version: "0.4.0",
7954
- description: "Persistent local loop and workflow runner for deterministic commands and headless AI coding agents",
7955
- type: "module",
7956
- main: "dist/index.js",
7957
- types: "dist/index.d.ts",
7958
- bin: {
7959
- loops: "dist/cli/index.js",
7960
- "loops-daemon": "dist/daemon/index.js",
7961
- "loops-mcp": "dist/mcp/index.js"
7962
- },
7963
- exports: {
7964
- ".": {
7965
- types: "./dist/index.d.ts",
7966
- import: "./dist/index.js"
7967
- },
7968
- "./sdk": {
7969
- types: "./dist/sdk/index.d.ts",
7970
- import: "./dist/sdk/index.js"
7971
- },
7972
- "./mcp": {
7973
- types: "./dist/mcp/index.d.ts",
7974
- import: "./dist/mcp/index.js"
7975
- },
7976
- "./storage": {
7977
- types: "./dist/lib/store.d.ts",
7978
- import: "./dist/lib/store.js"
7979
- }
7980
- },
7981
- files: [
7982
- "dist",
7983
- "README.md",
7984
- "CHANGELOG.md",
7985
- "docs",
7986
- "LICENSE"
7987
- ],
7988
- scripts: {
7989
- build: "rm -rf dist && bun build src/cli/index.ts src/daemon/index.ts src/mcp/index.ts src/index.ts src/sdk/index.ts src/lib/store.ts --outdir dist --target bun --packages external && chmod +x dist/cli/index.js dist/daemon/index.js dist/mcp/index.js && tsc -p tsconfig.build.json --emitDeclarationOnly --outDir dist",
7990
- "build:bin": "bun build src/cli/index.ts --compile --outfile dist/loops",
7991
- typecheck: "tsc --noEmit",
7992
- test: "bun test",
7993
- prepare: "test -d dist || bun run build",
7994
- "dev:cli": "bun run src/cli/index.ts",
7995
- "dev:daemon": "bun run src/daemon/index.ts",
7996
- prepublishOnly: "bun run build"
7997
- },
7998
- keywords: [
7999
- "loops",
8000
- "scheduler",
8001
- "daemon",
8002
- "agents",
8003
- "claude",
8004
- "cursor",
8005
- "codewith",
8006
- "opencode",
8007
- "cli"
8008
- ],
8009
- repository: {
8010
- type: "git",
8011
- url: "git+https://github.com/hasna/loops.git"
8012
- },
8013
- homepage: "https://github.com/hasna/loops#readme",
8014
- bugs: {
8015
- url: "https://github.com/hasna/loops/issues"
8016
- },
8017
- author: "Hasna <andrei@hasna.com>",
8018
- license: "Apache-2.0",
8019
- engines: {
8020
- bun: ">=1.0.0"
8021
- },
8022
- dependencies: {
8023
- "@hasna/events": "^0.1.9",
8024
- "@modelcontextprotocol/sdk": "^1.29.0",
8025
- "@openrouter/ai-sdk-provider": "2.9.1",
8026
- ai: "6.0.204",
8027
- commander: "^13.1.0",
8028
- zod: "4.4.3"
8029
- },
8030
- optionalDependencies: {
8031
- "@hasna/machines": "0.0.49"
8032
- },
8033
- devDependencies: {
8034
- "@types/bun": "latest",
8035
- typescript: "^5.7.3"
8036
- },
8037
- publishConfig: {
8038
- registry: "https://registry.npmjs.org",
8039
- access: "public"
8040
- }
8041
- };
8042
-
8043
- // src/lib/version.ts
8044
- function packageVersion() {
8045
- if (typeof package_default.version !== "string" || package_default.version.trim() === "")
8046
- throw new Error("package.json version is missing");
8047
- return package_default.version;
8048
- }
8049
8205
 
8050
8206
  // src/lib/templates.ts
8051
8207
  import { execFileSync } from "child_process";
8052
8208
  import { existsSync as existsSync7 } from "fs";
8053
- import { homedir as homedir3 } from "os";
8209
+ import { homedir as homedir4 } from "os";
8054
8210
  import { basename as basename4, isAbsolute as isAbsolute2, join as join7, relative, resolve as resolve4 } from "path";
8055
8211
 
8056
8212
  // src/lib/template-kit.ts
@@ -9031,10 +9187,10 @@ function normalizeWorktreeMode(mode) {
9031
9187
  }
9032
9188
  function defaultWorktreeRoot(root) {
9033
9189
  if (root?.trim()) {
9034
- const expanded = root.trim().replace(/^~(?=$|\/)/, homedir3());
9190
+ const expanded = root.trim().replace(/^~(?=$|\/)/, homedir4());
9035
9191
  return isAbsolute2(expanded) ? expanded : resolve4(expanded);
9036
9192
  }
9037
- return join7(homedir3(), ".hasna", "loops", "worktrees");
9193
+ return join7(homedir4(), ".hasna", "loops", "worktrees");
9038
9194
  }
9039
9195
  function gitRootFor(path) {
9040
9196
  if (!existsSync7(path))
@@ -11303,9 +11459,9 @@ import { resolve as resolve8 } from "path";
11303
11459
  import { closeSync, mkdtempSync as mkdtempSync2, openSync as openSync2, readFileSync as readFileSync9, rmSync as rmSync6 } from "fs";
11304
11460
  import { spawnSync as spawnSync8 } from "child_process";
11305
11461
  import { join as join10 } from "path";
11306
- import { tmpdir as tmpdir2 } from "os";
11462
+ import { homedir as homedir5, tmpdir as tmpdir2 } from "os";
11307
11463
  function defaultLoopsProject() {
11308
- return process.env.LOOPS_TASK_PROJECT || process.env.LOOPS_DATA_DIR || `${process.env.HOME ?? "/home/hasna"}/.hasna/loops`;
11464
+ return process.env.LOOPS_TASK_PROJECT || process.env.LOOPS_DATA_DIR || `${process.env.HOME || homedir5()}/.hasna/loops`;
11309
11465
  }
11310
11466
  function runLocalCommand(command, args, opts = {}) {
11311
11467
  const result = spawnSync8(command, args, {
@@ -12156,6 +12312,20 @@ function runAction(fn) {
12156
12312
  }
12157
12313
  };
12158
12314
  }
12315
+ function printDeploymentStatus(status, opts = {}) {
12316
+ if (isJson() || opts.json)
12317
+ console.log(JSON.stringify(status, null, 2));
12318
+ else {
12319
+ console.log(deploymentStatusLine(status));
12320
+ for (const warning of status.warnings)
12321
+ console.log(`warn ${warning}`);
12322
+ }
12323
+ }
12324
+ function deploymentStatusCommand(mode) {
12325
+ return (opts = {}) => {
12326
+ printDeploymentStatus(buildDeploymentStatus({ perspective: mode }), opts);
12327
+ };
12328
+ }
12159
12329
  function printCreatedLoop(loop, human, preflight) {
12160
12330
  if (preflight !== undefined)
12161
12331
  print({ loop: publicLoop(loop), preflight }, human);
@@ -12484,6 +12654,11 @@ var routes = program.command("routes").alias("route").description("create, inspe
12484
12654
  var events = program.command("events").description("(deprecated) Hasna event envelope aliases for 'routes create' and 'routes drain'");
12485
12655
  var machines = program.command("machines").description("inspect OpenMachines topology for loop assignment");
12486
12656
  var goal = program.command("goal").description("inspect goal runs");
12657
+ program.command("mode").description("show the active OpenLoops deployment mode").option("--json", "print JSON").action(runAction(deploymentStatusCommand()));
12658
+ var selfHosted = program.command("self-hosted").alias("selfhosted").description("inspect the self-hosted OpenLoops contract");
12659
+ selfHosted.command("status").option("--json", "print JSON").action(runAction(deploymentStatusCommand("self_hosted")));
12660
+ var cloud = program.command("cloud").description("inspect the hosted OpenLoops contract");
12661
+ cloud.command("status").option("--json", "print JSON").action(runAction(deploymentStatusCommand("cloud")));
12487
12662
  function formatTemplateVariable(template, name) {
12488
12663
  const variable = template.variables.find((entry) => entry.name === name);
12489
12664
  const placeholder = variable?.default ? variable.default : `<${name}>`;
@@ -7605,7 +7605,7 @@ function enableStartup(result) {
7605
7605
  // package.json
7606
7606
  var package_default = {
7607
7607
  name: "@hasna/loops",
7608
- version: "0.4.0",
7608
+ version: "0.4.1",
7609
7609
  description: "Persistent local loop and workflow runner for deterministic commands and headless AI coding agents",
7610
7610
  type: "module",
7611
7611
  main: "dist/index.js",
@@ -7613,6 +7613,8 @@ var package_default = {
7613
7613
  bin: {
7614
7614
  loops: "dist/cli/index.js",
7615
7615
  "loops-daemon": "dist/daemon/index.js",
7616
+ "loops-api": "dist/api/index.js",
7617
+ "loops-runner": "dist/runner/index.js",
7616
7618
  "loops-mcp": "dist/mcp/index.js"
7617
7619
  },
7618
7620
  exports: {
@@ -7628,6 +7630,18 @@ var package_default = {
7628
7630
  types: "./dist/mcp/index.d.ts",
7629
7631
  import: "./dist/mcp/index.js"
7630
7632
  },
7633
+ "./api": {
7634
+ types: "./dist/api/index.d.ts",
7635
+ import: "./dist/api/index.js"
7636
+ },
7637
+ "./runner": {
7638
+ types: "./dist/runner/index.d.ts",
7639
+ import: "./dist/runner/index.js"
7640
+ },
7641
+ "./mode": {
7642
+ types: "./dist/lib/mode.d.ts",
7643
+ import: "./dist/lib/mode.js"
7644
+ },
7631
7645
  "./storage": {
7632
7646
  types: "./dist/lib/store.d.ts",
7633
7647
  import: "./dist/lib/store.js"
@@ -7641,14 +7655,15 @@ var package_default = {
7641
7655
  "LICENSE"
7642
7656
  ],
7643
7657
  scripts: {
7644
- build: "rm -rf dist && bun build src/cli/index.ts src/daemon/index.ts src/mcp/index.ts src/index.ts src/sdk/index.ts src/lib/store.ts --outdir dist --target bun --packages external && chmod +x dist/cli/index.js dist/daemon/index.js dist/mcp/index.js && tsc -p tsconfig.build.json --emitDeclarationOnly --outDir dist",
7658
+ build: "rm -rf dist && bun build src/cli/index.ts src/daemon/index.ts src/api/index.ts src/runner/index.ts src/mcp/index.ts src/index.ts src/sdk/index.ts src/lib/store.ts src/lib/mode.ts --root src --outdir dist --target bun --packages external && chmod +x dist/cli/index.js dist/daemon/index.js dist/api/index.js dist/runner/index.js dist/mcp/index.js && tsc -p tsconfig.build.json --emitDeclarationOnly --outDir dist",
7645
7659
  "build:bin": "bun build src/cli/index.ts --compile --outfile dist/loops",
7646
7660
  typecheck: "tsc --noEmit",
7647
7661
  test: "bun test",
7662
+ "test:boundary": "bun run scripts/no-private-cloud-boundary.mjs",
7648
7663
  prepare: "test -d dist || bun run build",
7649
7664
  "dev:cli": "bun run src/cli/index.ts",
7650
7665
  "dev:daemon": "bun run src/daemon/index.ts",
7651
- prepublishOnly: "bun run build"
7666
+ prepublishOnly: "bun run build && bun run test:boundary"
7652
7667
  },
7653
7668
  keywords: [
7654
7669
  "loops",
package/dist/index.d.ts CHANGED
@@ -20,6 +20,8 @@ export { computeNextAfter, initialNextRun, nextCronRun, parseCron, parseDuration
20
20
  export { runLoopNow, tick } from "./lib/scheduler.js";
21
21
  export type { ManualRunSource, RunLoopNowDeps, RunLoopNowExecuted, RunLoopNowMode, RunLoopNowResult, RunLoopNowScheduled, } from "./lib/scheduler.js";
22
22
  export { Store } from "./lib/store.js";
23
+ export { LOOP_DEPLOYMENT_MODES, buildDeploymentStatus, deploymentStatusLine, loopControlPlaneConfig, normalizeLoopDeploymentMode, resolveLoopDeploymentMode, } from "./lib/mode.js";
24
+ export type { LoopControlPlaneConfig, LoopDeploymentMode, LoopDeploymentStatus, LoopModeResolution, LoopSourceOfTruth } from "./lib/mode.js";
23
25
  export { executeLoop, executeTarget, preflightTarget } from "./lib/executor.js";
24
26
  export { executeLoopTarget, executeWorkflow, preflightWorkflow } from "./lib/workflow-runner.js";
25
27
  export { workflowBodyFromJson, workflowExecutionOrder } from "./lib/workflow-spec.js";