@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/index.js CHANGED
@@ -3962,6 +3962,257 @@ class Store {
3962
3962
  this.db.close();
3963
3963
  }
3964
3964
  }
3965
+ // package.json
3966
+ var package_default = {
3967
+ name: "@hasna/loops",
3968
+ version: "0.4.1",
3969
+ description: "Persistent local loop and workflow runner for deterministic commands and headless AI coding agents",
3970
+ type: "module",
3971
+ main: "dist/index.js",
3972
+ types: "dist/index.d.ts",
3973
+ bin: {
3974
+ loops: "dist/cli/index.js",
3975
+ "loops-daemon": "dist/daemon/index.js",
3976
+ "loops-api": "dist/api/index.js",
3977
+ "loops-runner": "dist/runner/index.js",
3978
+ "loops-mcp": "dist/mcp/index.js"
3979
+ },
3980
+ exports: {
3981
+ ".": {
3982
+ types: "./dist/index.d.ts",
3983
+ import: "./dist/index.js"
3984
+ },
3985
+ "./sdk": {
3986
+ types: "./dist/sdk/index.d.ts",
3987
+ import: "./dist/sdk/index.js"
3988
+ },
3989
+ "./mcp": {
3990
+ types: "./dist/mcp/index.d.ts",
3991
+ import: "./dist/mcp/index.js"
3992
+ },
3993
+ "./api": {
3994
+ types: "./dist/api/index.d.ts",
3995
+ import: "./dist/api/index.js"
3996
+ },
3997
+ "./runner": {
3998
+ types: "./dist/runner/index.d.ts",
3999
+ import: "./dist/runner/index.js"
4000
+ },
4001
+ "./mode": {
4002
+ types: "./dist/lib/mode.d.ts",
4003
+ import: "./dist/lib/mode.js"
4004
+ },
4005
+ "./storage": {
4006
+ types: "./dist/lib/store.d.ts",
4007
+ import: "./dist/lib/store.js"
4008
+ }
4009
+ },
4010
+ files: [
4011
+ "dist",
4012
+ "README.md",
4013
+ "CHANGELOG.md",
4014
+ "docs",
4015
+ "LICENSE"
4016
+ ],
4017
+ scripts: {
4018
+ 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",
4019
+ "build:bin": "bun build src/cli/index.ts --compile --outfile dist/loops",
4020
+ typecheck: "tsc --noEmit",
4021
+ test: "bun test",
4022
+ "test:boundary": "bun run scripts/no-private-cloud-boundary.mjs",
4023
+ prepare: "test -d dist || bun run build",
4024
+ "dev:cli": "bun run src/cli/index.ts",
4025
+ "dev:daemon": "bun run src/daemon/index.ts",
4026
+ prepublishOnly: "bun run build && bun run test:boundary"
4027
+ },
4028
+ keywords: [
4029
+ "loops",
4030
+ "scheduler",
4031
+ "daemon",
4032
+ "agents",
4033
+ "claude",
4034
+ "cursor",
4035
+ "codewith",
4036
+ "opencode",
4037
+ "cli"
4038
+ ],
4039
+ repository: {
4040
+ type: "git",
4041
+ url: "git+https://github.com/hasna/loops.git"
4042
+ },
4043
+ homepage: "https://github.com/hasna/loops#readme",
4044
+ bugs: {
4045
+ url: "https://github.com/hasna/loops/issues"
4046
+ },
4047
+ author: "Hasna <andrei@hasna.com>",
4048
+ license: "Apache-2.0",
4049
+ engines: {
4050
+ bun: ">=1.0.0"
4051
+ },
4052
+ dependencies: {
4053
+ "@hasna/events": "^0.1.9",
4054
+ "@modelcontextprotocol/sdk": "^1.29.0",
4055
+ "@openrouter/ai-sdk-provider": "2.9.1",
4056
+ ai: "6.0.204",
4057
+ commander: "^13.1.0",
4058
+ zod: "4.4.3"
4059
+ },
4060
+ optionalDependencies: {
4061
+ "@hasna/machines": "0.0.49"
4062
+ },
4063
+ devDependencies: {
4064
+ "@types/bun": "latest",
4065
+ typescript: "^5.7.3"
4066
+ },
4067
+ publishConfig: {
4068
+ registry: "https://registry.npmjs.org",
4069
+ access: "public"
4070
+ }
4071
+ };
4072
+
4073
+ // src/lib/version.ts
4074
+ function packageVersion() {
4075
+ if (typeof package_default.version !== "string" || package_default.version.trim() === "")
4076
+ throw new Error("package.json version is missing");
4077
+ return package_default.version;
4078
+ }
4079
+
4080
+ // src/lib/mode.ts
4081
+ var LOOP_DEPLOYMENT_MODES = ["local", "self_hosted", "cloud"];
4082
+ var MODE_ENV_KEYS = ["LOOPS_MODE", "HASNA_LOOPS_MODE"];
4083
+ var API_URL_ENV_KEYS = ["LOOPS_API_URL", "HASNA_LOOPS_API_URL"];
4084
+ var CLOUD_API_URL_ENV_KEYS = ["LOOPS_CLOUD_API_URL", "HASNA_LOOPS_CLOUD_API_URL"];
4085
+ var DATABASE_URL_ENV_KEYS = ["LOOPS_DATABASE_URL", "HASNA_LOOPS_DATABASE_URL"];
4086
+ var API_TOKEN_ENV_KEYS = ["LOOPS_API_TOKEN", "HASNA_LOOPS_API_TOKEN"];
4087
+ var CLOUD_TOKEN_ENV_KEYS = ["LOOPS_CLOUD_TOKEN", "HASNA_LOOPS_CLOUD_TOKEN"];
4088
+ var TOKEN_ENV_KEYS = [...API_TOKEN_ENV_KEYS, ...CLOUD_TOKEN_ENV_KEYS];
4089
+ function envValue(env, keys) {
4090
+ for (const key of keys) {
4091
+ const value = env[key]?.trim();
4092
+ if (value)
4093
+ return { key, value };
4094
+ }
4095
+ return;
4096
+ }
4097
+ function normalizeLoopDeploymentMode(value) {
4098
+ const normalized = value.trim().toLowerCase().replace(/-/g, "_");
4099
+ if (normalized === "local")
4100
+ return "local";
4101
+ if (normalized === "self_hosted" || normalized === "selfhosted")
4102
+ return "self_hosted";
4103
+ if (normalized === "cloud" || normalized === "saas")
4104
+ return "cloud";
4105
+ throw new Error(`unsupported OpenLoops deployment mode "${value}"; expected local, self_hosted, or cloud`);
4106
+ }
4107
+ function resolveLoopDeploymentMode(env = process.env) {
4108
+ const explicitMode = envValue(env, MODE_ENV_KEYS);
4109
+ if (explicitMode) {
4110
+ return {
4111
+ deploymentMode: normalizeLoopDeploymentMode(explicitMode.value),
4112
+ source: explicitMode.key
4113
+ };
4114
+ }
4115
+ const cloudApiUrl = envValue(env, CLOUD_API_URL_ENV_KEYS);
4116
+ if (cloudApiUrl)
4117
+ return { deploymentMode: "cloud", source: cloudApiUrl.key };
4118
+ const apiUrl = envValue(env, API_URL_ENV_KEYS);
4119
+ if (apiUrl)
4120
+ return { deploymentMode: "self_hosted", source: apiUrl.key };
4121
+ const databaseUrl = envValue(env, DATABASE_URL_ENV_KEYS);
4122
+ if (databaseUrl)
4123
+ return { deploymentMode: "self_hosted", source: databaseUrl.key };
4124
+ return { deploymentMode: "local", source: "default" };
4125
+ }
4126
+ function loopControlPlaneConfig(env = process.env) {
4127
+ return {
4128
+ apiUrl: envValue(env, API_URL_ENV_KEYS)?.value,
4129
+ cloudApiUrl: envValue(env, CLOUD_API_URL_ENV_KEYS)?.value,
4130
+ databaseUrlPresent: Boolean(envValue(env, DATABASE_URL_ENV_KEYS)),
4131
+ apiAuthTokenPresent: Boolean(envValue(env, API_TOKEN_ENV_KEYS)),
4132
+ cloudAuthTokenPresent: Boolean(envValue(env, CLOUD_TOKEN_ENV_KEYS)),
4133
+ authTokenPresent: Boolean(envValue(env, TOKEN_ENV_KEYS))
4134
+ };
4135
+ }
4136
+ function sourceOfTruthForMode(mode) {
4137
+ if (mode === "local")
4138
+ return "local_sqlite";
4139
+ if (mode === "self_hosted")
4140
+ return "self_hosted_control_plane";
4141
+ return "cloud_control_plane";
4142
+ }
4143
+ function displayControlPlaneUrl(value) {
4144
+ if (!value)
4145
+ return;
4146
+ try {
4147
+ const url = new URL(value);
4148
+ const path = url.pathname === "/" ? "" : url.pathname.replace(/\/+$/g, "");
4149
+ return `${url.origin}${path}`;
4150
+ } catch {
4151
+ return "[invalid-url]";
4152
+ }
4153
+ }
4154
+ function buildDeploymentStatus(opts = {}) {
4155
+ const env = opts.env ?? process.env;
4156
+ const active = resolveLoopDeploymentMode(env);
4157
+ const deploymentMode = opts.perspective ?? active.deploymentMode;
4158
+ const config = loopControlPlaneConfig(env);
4159
+ const rawApiUrl = deploymentMode === "cloud" ? config.cloudApiUrl : config.apiUrl;
4160
+ const apiUrl = displayControlPlaneUrl(rawApiUrl);
4161
+ const controlPlaneKind = deploymentMode === "local" ? "none" : deploymentMode;
4162
+ const deploymentAuthTokenPresent = deploymentMode === "cloud" ? config.cloudAuthTokenPresent : deploymentMode === "self_hosted" ? config.apiAuthTokenPresent : false;
4163
+ const controlPlaneConfigured = deploymentMode === "local" ? false : deploymentMode === "self_hosted" ? Boolean(config.apiUrl || config.databaseUrlPresent) : Boolean(config.cloudApiUrl && deploymentAuthTokenPresent);
4164
+ const warnings = [];
4165
+ if (deploymentMode === "self_hosted" && !controlPlaneConfigured) {
4166
+ warnings.push("self_hosted mode needs LOOPS_API_URL or LOOPS_DATABASE_URL before it can become authoritative");
4167
+ }
4168
+ if (deploymentMode === "cloud" && config.apiUrl && !config.cloudApiUrl) {
4169
+ warnings.push("LOOPS_API_URL selects self_hosted; cloud mode uses LOOPS_CLOUD_API_URL");
4170
+ }
4171
+ if (deploymentMode === "cloud" && !config.cloudApiUrl) {
4172
+ warnings.push("cloud mode is a public contract until LOOPS_CLOUD_API_URL is configured");
4173
+ }
4174
+ if (deploymentMode === "cloud" && config.cloudApiUrl && !deploymentAuthTokenPresent) {
4175
+ warnings.push("cloud mode needs LOOPS_CLOUD_TOKEN or HASNA_LOOPS_CLOUD_TOKEN before it can become ready");
4176
+ }
4177
+ if (opts.perspective && opts.perspective !== active.deploymentMode) {
4178
+ warnings.push(`active deployment mode is ${active.deploymentMode}; this is the ${opts.perspective} perspective`);
4179
+ }
4180
+ return {
4181
+ packageVersion: packageVersion(),
4182
+ deploymentMode,
4183
+ activeDeploymentMode: active.deploymentMode,
4184
+ active: deploymentMode === active.deploymentMode,
4185
+ deploymentModeSource: active.source,
4186
+ sourceOfTruth: sourceOfTruthForMode(deploymentMode),
4187
+ localStore: {
4188
+ role: deploymentMode === "local" ? "authoritative" : "cache_and_spool"
4189
+ },
4190
+ controlPlane: {
4191
+ kind: controlPlaneKind,
4192
+ configured: controlPlaneConfigured,
4193
+ apiUrl,
4194
+ databaseUrlPresent: config.databaseUrlPresent,
4195
+ authTokenPresent: deploymentAuthTokenPresent
4196
+ },
4197
+ runner: {
4198
+ required: deploymentMode !== "local",
4199
+ role: deploymentMode === "local" ? "daemon" : "control_plane_worker"
4200
+ },
4201
+ warnings
4202
+ };
4203
+ }
4204
+ function deploymentStatusLine(status) {
4205
+ const configured = status.controlPlane.kind === "none" ? "none" : String(status.controlPlane.configured);
4206
+ const active = status.active ? "active" : `active=${status.activeDeploymentMode}`;
4207
+ return [
4208
+ `deploymentMode=${status.deploymentMode}`,
4209
+ active,
4210
+ `source=${status.deploymentModeSource}`,
4211
+ `truth=${status.sourceOfTruth}`,
4212
+ `local=${status.localStore.role}`,
4213
+ `control_plane=${configured}`
4214
+ ].join(" ");
4215
+ }
3965
4216
 
3966
4217
  // src/mcp/index.ts
3967
4218
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
@@ -7323,105 +7574,6 @@ async function tick(deps) {
7323
7574
  }
7324
7575
  return { claimed, completed, skipped, recovered, expired };
7325
7576
  }
7326
- // package.json
7327
- var package_default = {
7328
- name: "@hasna/loops",
7329
- version: "0.4.0",
7330
- description: "Persistent local loop and workflow runner for deterministic commands and headless AI coding agents",
7331
- type: "module",
7332
- main: "dist/index.js",
7333
- types: "dist/index.d.ts",
7334
- bin: {
7335
- loops: "dist/cli/index.js",
7336
- "loops-daemon": "dist/daemon/index.js",
7337
- "loops-mcp": "dist/mcp/index.js"
7338
- },
7339
- exports: {
7340
- ".": {
7341
- types: "./dist/index.d.ts",
7342
- import: "./dist/index.js"
7343
- },
7344
- "./sdk": {
7345
- types: "./dist/sdk/index.d.ts",
7346
- import: "./dist/sdk/index.js"
7347
- },
7348
- "./mcp": {
7349
- types: "./dist/mcp/index.d.ts",
7350
- import: "./dist/mcp/index.js"
7351
- },
7352
- "./storage": {
7353
- types: "./dist/lib/store.d.ts",
7354
- import: "./dist/lib/store.js"
7355
- }
7356
- },
7357
- files: [
7358
- "dist",
7359
- "README.md",
7360
- "CHANGELOG.md",
7361
- "docs",
7362
- "LICENSE"
7363
- ],
7364
- scripts: {
7365
- 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",
7366
- "build:bin": "bun build src/cli/index.ts --compile --outfile dist/loops",
7367
- typecheck: "tsc --noEmit",
7368
- test: "bun test",
7369
- prepare: "test -d dist || bun run build",
7370
- "dev:cli": "bun run src/cli/index.ts",
7371
- "dev:daemon": "bun run src/daemon/index.ts",
7372
- prepublishOnly: "bun run build"
7373
- },
7374
- keywords: [
7375
- "loops",
7376
- "scheduler",
7377
- "daemon",
7378
- "agents",
7379
- "claude",
7380
- "cursor",
7381
- "codewith",
7382
- "opencode",
7383
- "cli"
7384
- ],
7385
- repository: {
7386
- type: "git",
7387
- url: "git+https://github.com/hasna/loops.git"
7388
- },
7389
- homepage: "https://github.com/hasna/loops#readme",
7390
- bugs: {
7391
- url: "https://github.com/hasna/loops/issues"
7392
- },
7393
- author: "Hasna <andrei@hasna.com>",
7394
- license: "Apache-2.0",
7395
- engines: {
7396
- bun: ">=1.0.0"
7397
- },
7398
- dependencies: {
7399
- "@hasna/events": "^0.1.9",
7400
- "@modelcontextprotocol/sdk": "^1.29.0",
7401
- "@openrouter/ai-sdk-provider": "2.9.1",
7402
- ai: "6.0.204",
7403
- commander: "^13.1.0",
7404
- zod: "4.4.3"
7405
- },
7406
- optionalDependencies: {
7407
- "@hasna/machines": "0.0.49"
7408
- },
7409
- devDependencies: {
7410
- "@types/bun": "latest",
7411
- typescript: "^5.7.3"
7412
- },
7413
- publishConfig: {
7414
- registry: "https://registry.npmjs.org",
7415
- access: "public"
7416
- }
7417
- };
7418
-
7419
- // src/lib/version.ts
7420
- function packageVersion() {
7421
- if (typeof package_default.version !== "string" || package_default.version.trim() === "")
7422
- throw new Error("package.json version is missing");
7423
- return package_default.version;
7424
- }
7425
7577
 
7426
7578
  // src/mcp/index.ts
7427
7579
  var LOOP_STATUSES = ["active", "paused", "stopped", "expired"];
@@ -8141,15 +8293,15 @@ function openAutomationsRuntimeBinding(overrides = {}) {
8141
8293
  failCommand: "automations queue fail",
8142
8294
  eventHandoff: {
8143
8295
  envelopeCommand: "automations webhooks event",
8144
- handlerCommand: "loops events handle generic",
8145
- pipeExample: "automations --json webhooks event <route> --body-json '<json>' | loops --json events handle generic",
8296
+ handlerCommand: "loops routes create generic",
8297
+ pipeExample: "automations --json webhooks event <route> --body-json '<json>' | loops --json routes create generic",
8146
8298
  boundary: "Use only for explicit event-envelope workflow handoff. OpenAutomations still owns deterministic automation materialization and queue state; OpenLoops owns workflow invocation."
8147
8299
  },
8148
8300
  requiredEnvironment: ["HASNA_AUTOMATIONS_DIR"],
8149
8301
  guarantees: [
8150
8302
  "OpenAutomations owns automation specs, run materialization, queue state, DLQ, replay, idempotency, and approvals.",
8151
8303
  "OpenLoops may execute claimed actions through explicit command or SDK handoff only.",
8152
- "OpenLoops may consume exported event envelopes only through explicit events handle commands.",
8304
+ "OpenLoops may consume exported event envelopes only through explicit routes create commands.",
8153
8305
  "Workers must complete or fail actions by action id and runner id so OpenAutomations can enforce queue leases."
8154
8306
  ],
8155
8307
  nonGoals: [
@@ -9955,6 +10107,7 @@ function renderLoopTemplate(id, values, opts = {}) {
9955
10107
  }
9956
10108
  // src/lib/hygiene.ts
9957
10109
  import { basename as basename4 } from "path";
10110
+ import { homedir as homedir4 } from "os";
9958
10111
  var PROVIDER_TOKENS = new Set([
9959
10112
  "codewith",
9960
10113
  "claude",
@@ -9969,11 +10122,14 @@ var PROVIDER_TOKENS = new Set([
9969
10122
  var REPO_GENERIC_TOKENS = new Set(["repo", "repoops"]);
9970
10123
  var CADENCE_SUFFIX_TOKENS = new Set(["hourly", "daily", "weekly", "monthly"]);
9971
10124
  var CADENCE_SUFFIX_PATTERN = /^(?:every-?)?\d+(?:s|m|h|d|w)$/;
10125
+ function userHome() {
10126
+ return process.env.HOME || homedir4();
10127
+ }
9972
10128
  function slugify(value) {
9973
10129
  return value.normalize("NFKD").replace(/[^\w\s.-]/g, "-").replace(/[_\s.:/]+/g, "-").replace(/[^a-zA-Z0-9-]+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").toLowerCase();
9974
10130
  }
9975
10131
  function repoSlugFromCwd(cwd) {
9976
- if (!cwd || cwd === process.env.HOME || cwd === "/home/hasna")
10132
+ if (!cwd || cwd === userHome())
9977
10133
  return "";
9978
10134
  if (cwd.includes("/.hasna/loops/"))
9979
10135
  return "";
@@ -10156,7 +10312,7 @@ function commandText(loop) {
10156
10312
  return [loop.target.command, ...loop.target.args ?? []].join(" ");
10157
10313
  }
10158
10314
  function scriptNeedles(scriptsDir) {
10159
- const home = process.env.HOME ?? "/home/hasna";
10315
+ const home = userHome();
10160
10316
  const normalized = scriptsDir.replace(/\/+$/g, "");
10161
10317
  const values = [
10162
10318
  normalized,
@@ -10174,7 +10330,7 @@ function scriptNeedles(scriptsDir) {
10174
10330
  return [...new Set(values)];
10175
10331
  }
10176
10332
  function buildScriptInventoryReport(store, opts = {}) {
10177
- const scriptsDir = opts.scriptsDir ?? `${process.env.HOME ?? "/home/hasna"}/.hasna/loops/scripts`;
10333
+ const scriptsDir = opts.scriptsDir ?? `${userHome()}/.hasna/loops/scripts`;
10178
10334
  const needles = scriptNeedles(scriptsDir);
10179
10335
  const loops2 = managedLoops(store, { includeInactive: opts.includeInactive, includeStopped: true, limit: opts.limit });
10180
10336
  const scriptBacked = loops2.map((loop) => {
@@ -10210,6 +10366,7 @@ export {
10210
10366
  runDoctor,
10211
10367
  rollupSummary,
10212
10368
  resolveLoopMachine,
10369
+ resolveLoopDeploymentMode,
10213
10370
  resolveGoalModel,
10214
10371
  renderTodosTaskWorkerVerifierWorkflow,
10215
10372
  renderLoopTemplate,
@@ -10222,8 +10379,10 @@ export {
10222
10379
  parseDuration,
10223
10380
  parseCron,
10224
10381
  openAutomationsRuntimeBinding,
10382
+ normalizeLoopDeploymentMode,
10225
10383
  nextCronRun,
10226
10384
  loops,
10385
+ loopControlPlaneConfig,
10227
10386
  listToolsForCli,
10228
10387
  listOpenMachines,
10229
10388
  listLoopTemplates,
@@ -10235,6 +10394,7 @@ export {
10235
10394
  executeTarget,
10236
10395
  executeLoopTarget,
10237
10396
  executeLoop,
10397
+ deploymentStatusLine,
10238
10398
  createLoopsMcpServer,
10239
10399
  computeNextAfter,
10240
10400
  classifyRunFailure,
@@ -10242,12 +10402,14 @@ export {
10242
10402
  buildNameHygieneReport,
10243
10403
  buildHealthReport,
10244
10404
  buildDuplicateOverlapReport,
10405
+ buildDeploymentStatus,
10245
10406
  ValidationError,
10246
10407
  TODOS_TASK_WORKER_VERIFIER_TEMPLATE_ID,
10247
10408
  Store,
10248
10409
  LoopsClient,
10249
10410
  LoopNotFoundError,
10250
10411
  LoopArchivedError,
10412
+ LOOP_DEPLOYMENT_MODES,
10251
10413
  LOOPS_MCP_TOOLS,
10252
10414
  EVENT_WORKER_VERIFIER_TEMPLATE_ID,
10253
10415
  CodedError,
@@ -0,0 +1,48 @@
1
+ export declare const LOOP_DEPLOYMENT_MODES: readonly ["local", "self_hosted", "cloud"];
2
+ export type LoopDeploymentMode = (typeof LOOP_DEPLOYMENT_MODES)[number];
3
+ export type LoopSourceOfTruth = "local_sqlite" | "self_hosted_control_plane" | "cloud_control_plane";
4
+ export interface LoopModeResolution {
5
+ deploymentMode: LoopDeploymentMode;
6
+ source: string;
7
+ }
8
+ export interface LoopControlPlaneConfig {
9
+ apiUrl?: string;
10
+ cloudApiUrl?: string;
11
+ databaseUrlPresent: boolean;
12
+ apiAuthTokenPresent: boolean;
13
+ cloudAuthTokenPresent: boolean;
14
+ authTokenPresent: boolean;
15
+ }
16
+ export interface LoopDeploymentStatus {
17
+ packageVersion: string;
18
+ deploymentMode: LoopDeploymentMode;
19
+ activeDeploymentMode: LoopDeploymentMode;
20
+ active: boolean;
21
+ deploymentModeSource: string;
22
+ sourceOfTruth: LoopSourceOfTruth;
23
+ localStore: {
24
+ role: "authoritative" | "cache_and_spool";
25
+ };
26
+ controlPlane: {
27
+ kind: "none" | "self_hosted" | "cloud";
28
+ configured: boolean;
29
+ apiUrl?: string;
30
+ databaseUrlPresent: boolean;
31
+ authTokenPresent: boolean;
32
+ };
33
+ runner: {
34
+ required: boolean;
35
+ role: "daemon" | "control_plane_worker";
36
+ };
37
+ warnings: string[];
38
+ }
39
+ type Env = Record<string, string | undefined>;
40
+ export declare function normalizeLoopDeploymentMode(value: string): LoopDeploymentMode;
41
+ export declare function resolveLoopDeploymentMode(env?: Env): LoopModeResolution;
42
+ export declare function loopControlPlaneConfig(env?: Env): LoopControlPlaneConfig;
43
+ export declare function buildDeploymentStatus(opts?: {
44
+ env?: Env;
45
+ perspective?: LoopDeploymentMode;
46
+ }): LoopDeploymentStatus;
47
+ export declare function deploymentStatusLine(status: LoopDeploymentStatus): string;
48
+ export {};