@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/CHANGELOG.md +18 -0
- package/README.md +75 -33
- package/dist/api/index.d.ts +11 -0
- package/dist/api/index.js +333 -0
- package/dist/cli/index.js +282 -107
- package/dist/daemon/index.js +18 -3
- package/dist/index.d.ts +2 -0
- package/dist/index.js +267 -105
- package/dist/lib/mode.d.ts +48 -0
- package/dist/lib/mode.js +260 -0
- package/dist/mcp/index.js +18 -3
- package/dist/runner/index.d.ts +9 -0
- package/dist/runner/index.js +295 -0
- package/dist/sdk/index.js +3 -3
- package/dist/types.d.ts +1 -1
- package/docs/DEPLOYMENT_MODES.md +140 -0
- package/docs/USAGE.md +66 -32
- package/package.json +18 -3
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 ===
|
|
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 =
|
|
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 ?? `${
|
|
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
|
|
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(/^~(?=$|\/)/,
|
|
9190
|
+
const expanded = root.trim().replace(/^~(?=$|\/)/, homedir4());
|
|
9035
9191
|
return isAbsolute2(expanded) ? expanded : resolve4(expanded);
|
|
9036
9192
|
}
|
|
9037
|
-
return join7(
|
|
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
|
|
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}>`;
|
package/dist/daemon/index.js
CHANGED
|
@@ -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.
|
|
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";
|