lastlight-shared 0.1.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Last Light Contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Config types the workflow loader needs, lifted out of `lastlight-core`'s
3
+ * `config/config.ts` so `shared` never depends back on core (locked decision
4
+ * 11). Core re-exports these from `lastlight-shared` so its own
5
+ * `config/config.js` import surface is unchanged.
6
+ */
7
+ export interface DisabledConfig {
8
+ workflows: string[];
9
+ crons: string[];
10
+ prompts: string[];
11
+ skills: string[];
12
+ agentContext: string[];
13
+ }
14
+ export interface RouteConfig {
15
+ github: Record<string, string>;
16
+ slack: Record<string, string>;
17
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Config types the workflow loader needs, lifted out of `lastlight-core`'s
3
+ * `config/config.ts` so `shared` never depends back on core (locked decision
4
+ * 11). Core re-exports these from `lastlight-shared` so its own
5
+ * `config/config.js` import surface is unchanged.
6
+ */
7
+ export {};
8
+ //# sourceMappingURL=config-types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config-types.js","sourceRoot":"","sources":["../src/config-types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Pick the *commit* SHA for tag `pin` from `git ls-remote … 'refs/tags/<pin>*'`
3
+ * output. Annotated tags emit two rows — the tag object (`refs/tags/<pin>`) and
4
+ * the peeled commit (`refs/tags/<pin>^{}`); only the glob pattern surfaces the
5
+ * peeled row (an exact `refs/tags/<pin>` hides it). We want the commit, since
6
+ * that's what `git checkout <pin>` lands HEAD on and what an image built at the
7
+ * tag is stamped with. Preference: peeled commit → exact tag ref (lightweight
8
+ * tag / branch) → first row. Null when nothing looks like a SHA.
9
+ */
10
+ export declare function pickTagCommit(lsRemoteOut: string | null, pin: string): string | null;
11
+ /**
12
+ * The core-version pin for the overlay at `overlayDir`, or `null` to track main.
13
+ * `LASTLIGHT_CORE_VERSION` wins over the file. Missing / unreadable / malformed
14
+ * config.yaml is treated as unpinned (never throws).
15
+ */
16
+ export declare function readCorePin(overlayDir: string): string | null;
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Read the core-version pin an overlay declares.
3
+ *
4
+ * The overlay's `config.yaml` may carry a `deploy.version` — a git tag/ref
5
+ * (e.g. `v0.10.6`) that pins which core version this instance runs. When set,
6
+ * `lastlight server update|setup` checks the core repo out at that ref instead
7
+ * of tracking `main`, and the dashboard drift banner compares the running image
8
+ * to the pinned tag rather than to `main` HEAD.
9
+ *
10
+ * This reader is deliberately standalone (a raw YAML read, not the full
11
+ * default/overlay/env config machinery) because its two callers run in very
12
+ * different contexts against a raw overlay checkout path:
13
+ * - the host-local `lastlight server` CLI (no harness / runtime config) —
14
+ * against `<serverHome>/instance`;
15
+ * - the in-container harness banner (`src/admin/version.ts`) — against the
16
+ * mounted `LASTLIGHT_OVERLAY_DIR` (`/app/instance`).
17
+ *
18
+ * `LASTLIGHT_CORE_VERSION` (env) overrides the file so CI can pin without
19
+ * editing config.yaml. Unset, or the sentinels `main` / `latest`, mean "track
20
+ * main" and return `null`.
21
+ */
22
+ import { readFileSync } from "node:fs";
23
+ import { join } from "node:path";
24
+ import { parse as parseYaml } from "yaml";
25
+ /** Normalize a raw pin value: trim, treat empty / `main` / `latest` as null. */
26
+ function normalizePin(raw) {
27
+ if (typeof raw !== "string")
28
+ return null;
29
+ const v = raw.trim();
30
+ if (!v)
31
+ return null;
32
+ const lower = v.toLowerCase();
33
+ if (lower === "main" || lower === "latest")
34
+ return null;
35
+ return v;
36
+ }
37
+ /**
38
+ * Pick the *commit* SHA for tag `pin` from `git ls-remote … 'refs/tags/<pin>*'`
39
+ * output. Annotated tags emit two rows — the tag object (`refs/tags/<pin>`) and
40
+ * the peeled commit (`refs/tags/<pin>^{}`); only the glob pattern surfaces the
41
+ * peeled row (an exact `refs/tags/<pin>` hides it). We want the commit, since
42
+ * that's what `git checkout <pin>` lands HEAD on and what an image built at the
43
+ * tag is stamped with. Preference: peeled commit → exact tag ref (lightweight
44
+ * tag / branch) → first row. Null when nothing looks like a SHA.
45
+ */
46
+ export function pickTagCommit(lsRemoteOut, pin) {
47
+ if (!lsRemoteOut)
48
+ return null;
49
+ const rows = lsRemoteOut
50
+ .split("\n")
51
+ .map((l) => l.trim())
52
+ .filter(Boolean)
53
+ .map((l) => {
54
+ const [sha, ref] = l.split(/\s+/);
55
+ return { sha: sha ?? "", ref: ref ?? "" };
56
+ });
57
+ const peeled = rows.find((r) => r.ref === `refs/tags/${pin}^{}`);
58
+ const exact = rows.find((r) => r.ref === `refs/tags/${pin}`);
59
+ const chosen = (peeled ?? exact ?? rows[0])?.sha ?? "";
60
+ return /^[0-9a-f]{7,40}$/i.test(chosen) ? chosen : null;
61
+ }
62
+ /**
63
+ * The core-version pin for the overlay at `overlayDir`, or `null` to track main.
64
+ * `LASTLIGHT_CORE_VERSION` wins over the file. Missing / unreadable / malformed
65
+ * config.yaml is treated as unpinned (never throws).
66
+ */
67
+ export function readCorePin(overlayDir) {
68
+ const fromEnv = normalizePin(process.env.LASTLIGHT_CORE_VERSION);
69
+ if (fromEnv)
70
+ return fromEnv;
71
+ try {
72
+ const parsed = parseYaml(readFileSync(join(overlayDir, "config.yaml"), "utf8"));
73
+ if (parsed && typeof parsed === "object") {
74
+ const deploy = parsed.deploy;
75
+ if (deploy && typeof deploy === "object") {
76
+ return normalizePin(deploy.version);
77
+ }
78
+ }
79
+ }
80
+ catch {
81
+ /* no overlay / no config.yaml / parse error → track main */
82
+ }
83
+ return null;
84
+ }
85
+ //# sourceMappingURL=core-pin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"core-pin.js","sourceRoot":"","sources":["../src/core-pin.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,KAAK,IAAI,SAAS,EAAE,MAAM,MAAM,CAAC;AAE1C,gFAAgF;AAChF,SAAS,YAAY,CAAC,GAAY;IAChC,IAAI,OAAO,GAAG,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC;IACzC,MAAM,CAAC,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC;IACrB,IAAI,CAAC,CAAC;QAAE,OAAO,IAAI,CAAC;IACpB,MAAM,KAAK,GAAG,CAAC,CAAC,WAAW,EAAE,CAAC;IAC9B,IAAI,KAAK,KAAK,MAAM,IAAI,KAAK,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC;IACxD,OAAO,CAAC,CAAC;AACX,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,aAAa,CAAC,WAA0B,EAAE,GAAW;IACnE,IAAI,CAAC,WAAW;QAAE,OAAO,IAAI,CAAC;IAC9B,MAAM,IAAI,GAAG,WAAW;SACrB,KAAK,CAAC,IAAI,CAAC;SACX,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;SACpB,MAAM,CAAC,OAAO,CAAC;SACf,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;QACT,MAAM,CAAC,GAAG,EAAE,GAAG,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QAClC,OAAO,EAAE,GAAG,EAAE,GAAG,IAAI,EAAE,EAAE,GAAG,EAAE,GAAG,IAAI,EAAE,EAAE,CAAC;IAC5C,CAAC,CAAC,CAAC;IACL,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,KAAK,aAAa,GAAG,KAAK,CAAC,CAAC;IACjE,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,KAAK,aAAa,GAAG,EAAE,CAAC,CAAC;IAC7D,MAAM,MAAM,GAAG,CAAC,MAAM,IAAI,KAAK,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,GAAG,IAAI,EAAE,CAAC;IACvD,OAAO,mBAAmB,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC;AAC1D,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,WAAW,CAAC,UAAkB;IAC5C,MAAM,OAAO,GAAG,YAAY,CAAC,OAAO,CAAC,GAAG,CAAC,sBAAsB,CAAC,CAAC;IACjE,IAAI,OAAO;QAAE,OAAO,OAAO,CAAC;IAC5B,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,SAAS,CAAC,YAAY,CAAC,IAAI,CAAC,UAAU,EAAE,aAAa,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC;QAChF,IAAI,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE,CAAC;YACzC,MAAM,MAAM,GAAI,MAAkC,CAAC,MAAM,CAAC;YAC1D,IAAI,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE,CAAC;gBACzC,OAAO,YAAY,CAAE,MAAkC,CAAC,OAAO,CAAC,CAAC;YACnE,CAAC;QACH,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,4DAA4D;IAC9D,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * `lastlight-shared` — the light modules shared by the published `lastlight`
3
+ * CLI and `lastlight-core`. Node built-ins + `yaml` / `@clack/prompts` /
4
+ * `chalk` / `@earendil-works/pi-ai` (oauth subpath) / `lastlight-workflow-engine`
5
+ * (workflow schema) only — no heavy runtime deps (better-sqlite3, slack,
6
+ * octokit, hono, otel, agentic-pi) are ever reachable from here (fence F4).
7
+ *
8
+ * Invariant: no edge from `shared` back to `lastlight-core`.
9
+ *
10
+ * `./oauth` and `./workflow-loader` are also exposed as dedicated subpaths so
11
+ * core can keep thin re-export shims at their original file paths (preserving
12
+ * `vi.mock` targets + module-singleton identity) without leaking the whole
13
+ * barrel through those paths.
14
+ */
15
+ export * from "./providers.js";
16
+ export * from "./oauth.js";
17
+ export * from "./overlay-bootstrap.js";
18
+ export * from "./overlay-assets.js";
19
+ export * from "./core-pin.js";
20
+ export * from "./workflow-loader.js";
21
+ export * from "./config-types.js";
package/dist/index.js ADDED
@@ -0,0 +1,22 @@
1
+ /**
2
+ * `lastlight-shared` — the light modules shared by the published `lastlight`
3
+ * CLI and `lastlight-core`. Node built-ins + `yaml` / `@clack/prompts` /
4
+ * `chalk` / `@earendil-works/pi-ai` (oauth subpath) / `lastlight-workflow-engine`
5
+ * (workflow schema) only — no heavy runtime deps (better-sqlite3, slack,
6
+ * octokit, hono, otel, agentic-pi) are ever reachable from here (fence F4).
7
+ *
8
+ * Invariant: no edge from `shared` back to `lastlight-core`.
9
+ *
10
+ * `./oauth` and `./workflow-loader` are also exposed as dedicated subpaths so
11
+ * core can keep thin re-export shims at their original file paths (preserving
12
+ * `vi.mock` targets + module-singleton identity) without leaking the whole
13
+ * barrel through those paths.
14
+ */
15
+ export * from "./providers.js";
16
+ export * from "./oauth.js";
17
+ export * from "./overlay-bootstrap.js";
18
+ export * from "./overlay-assets.js";
19
+ export * from "./core-pin.js";
20
+ export * from "./workflow-loader.js";
21
+ export * from "./config-types.js";
22
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,cAAc,gBAAgB,CAAC;AAC/B,cAAc,YAAY,CAAC;AAC3B,cAAc,wBAAwB,CAAC;AACvC,cAAc,qBAAqB,CAAC;AACpC,cAAc,eAAe,CAAC;AAC9B,cAAc,sBAAsB,CAAC;AACrC,cAAc,mBAAmB,CAAC"}
@@ -0,0 +1,43 @@
1
+ import { getOAuthProvider, getOAuthProviders, type OAuthCredentials } from "@earendil-works/pi-ai/oauth";
2
+ /** Stored form — pi-ai's CLI tags each entry with `type: "oauth"`; we match it. */
3
+ export type StoredCredentials = OAuthCredentials & {
4
+ type?: string;
5
+ };
6
+ export type AuthMap = Record<string, StoredCredentials>;
7
+ /** OAuth providers that CANNOT fall back to an API key — login is mandatory. */
8
+ export declare const OAUTH_ONLY_PROVIDERS: ReadonlySet<string>;
9
+ /** OAuth provider id backing a model spec, or undefined if it's API-key based. */
10
+ export declare function oauthProviderIdForModel(spec: string): string | undefined;
11
+ /**
12
+ * The env var pi-ai reads inside a sandbox for a provider's OAuth token, when
13
+ * one exists. Returns undefined for providers with no env-var route (Codex),
14
+ * which therefore cannot authenticate in the agentic-pi sandbox.
15
+ */
16
+ export declare function oauthEnvVarForProvider(id: string): string | undefined;
17
+ /**
18
+ * Resolve the credential-store path. Precedence:
19
+ * 1. explicit argument (a caller-computed path),
20
+ * 2. `LASTLIGHT_AUTH_FILE` (hard override),
21
+ * 3. `<stateDir | $STATE_DIR | ./data>/auth.json`.
22
+ * The CLI writes here and the harness reads here, so both must agree; passing
23
+ * the harness's resolved `stateDir` keeps them aligned even if the process cwd
24
+ * differs.
25
+ */
26
+ export declare function resolveAuthFile(explicit?: string, stateDir?: string): string;
27
+ export declare function loadAuthMap(file?: string, stateDir?: string): AuthMap;
28
+ export declare function saveAuthMap(map: AuthMap, file?: string, stateDir?: string): void;
29
+ export declare function hasOAuthCredentials(id: string, file?: string, stateDir?: string): boolean;
30
+ export interface OAuthKeyResult {
31
+ apiKey: string;
32
+ credentials: OAuthCredentials;
33
+ }
34
+ /**
35
+ * Resolve a usable API key for an OAuth provider from stored credentials,
36
+ * refreshing an expired token and persisting the rotated credentials back to
37
+ * the store. Returns null when nothing is stored for `id`. Throws only if a
38
+ * refresh actually fails (expired refresh token, revoked grant) — callers
39
+ * should surface that as "re-run login".
40
+ */
41
+ export declare function resolveOAuthApiKey(id: string, file?: string, stateDir?: string): Promise<OAuthKeyResult | null>;
42
+ /** Re-exported so callers depend on this module, not pi-ai's oauth entry directly. */
43
+ export { getOAuthProvider, getOAuthProviders };
package/dist/oauth.js ADDED
@@ -0,0 +1,103 @@
1
+ /**
2
+ * OAuth credential management for LLM providers that authenticate with a
3
+ * subscription login instead of a static API key — ChatGPT Plus/Pro (Codex),
4
+ * Claude Pro/Max, and GitHub Copilot.
5
+ *
6
+ * pi-ai (`@earendil-works/pi-ai/oauth`) owns the actual OAuth flows, token
7
+ * refresh, and credential→apiKey conversion; this module is the thin Last
8
+ * Light layer on top:
9
+ * - a single on-disk credential store (`auth.json`, same JSON shape pi-ai's
10
+ * own CLI writes) resolved under `$STATE_DIR` so the CLI (writer) and the
11
+ * running harness (reader) agree on one path,
12
+ * - `resolveOAuthApiKey()` — refresh-if-expired + persist rotated creds +
13
+ * return a usable key, used by the in-process chat path,
14
+ * - the model-prefix → provider-id map and the sandbox env-var route so the
15
+ * chat and sandbox executors can both find the right credential.
16
+ *
17
+ * Two consumption seams with different reach:
18
+ * - **chat** (in-process pi-ai) — passes `apiKey` in the stream options, so
19
+ * ALL three OAuth providers work, Codex included.
20
+ * - **sandbox** (agentic-pi) — has no apiKey option; it reads provider creds
21
+ * from env only. pi-ai honours `ANTHROPIC_OAUTH_TOKEN` and
22
+ * `COPILOT_GITHUB_TOKEN`, but Codex (chatgpt.com backend) has no env route,
23
+ * so a Codex model cannot run in the sandbox. See `oauthEnvVarForProvider`.
24
+ */
25
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
26
+ import { dirname, resolve } from "node:path";
27
+ import { getOAuthApiKey, getOAuthProvider, getOAuthProviders, } from "@earendil-works/pi-ai/oauth";
28
+ import { OAUTH_PROVIDERS, oauthProviderByModelPrefix, oauthProviderById } from "./providers.js";
29
+ /** OAuth providers that CANNOT fall back to an API key — login is mandatory. */
30
+ export const OAUTH_ONLY_PROVIDERS = new Set(OAUTH_PROVIDERS.filter((p) => p.oauthOnly).map((p) => p.id));
31
+ /** OAuth provider id backing a model spec, or undefined if it's API-key based. */
32
+ export function oauthProviderIdForModel(spec) {
33
+ const prefix = spec.includes("/") ? spec.slice(0, spec.indexOf("/")) : spec;
34
+ return oauthProviderByModelPrefix(prefix)?.id;
35
+ }
36
+ /**
37
+ * The env var pi-ai reads inside a sandbox for a provider's OAuth token, when
38
+ * one exists. Returns undefined for providers with no env-var route (Codex),
39
+ * which therefore cannot authenticate in the agentic-pi sandbox.
40
+ */
41
+ export function oauthEnvVarForProvider(id) {
42
+ return oauthProviderById(id)?.sandboxEnvVar ?? undefined;
43
+ }
44
+ /**
45
+ * Resolve the credential-store path. Precedence:
46
+ * 1. explicit argument (a caller-computed path),
47
+ * 2. `LASTLIGHT_AUTH_FILE` (hard override),
48
+ * 3. `<stateDir | $STATE_DIR | ./data>/auth.json`.
49
+ * The CLI writes here and the harness reads here, so both must agree; passing
50
+ * the harness's resolved `stateDir` keeps them aligned even if the process cwd
51
+ * differs.
52
+ */
53
+ export function resolveAuthFile(explicit, stateDir) {
54
+ if (explicit)
55
+ return resolve(explicit);
56
+ if (process.env.LASTLIGHT_AUTH_FILE)
57
+ return resolve(process.env.LASTLIGHT_AUTH_FILE);
58
+ return resolve(stateDir || process.env.STATE_DIR || "data", "auth.json");
59
+ }
60
+ export function loadAuthMap(file, stateDir) {
61
+ const path = resolveAuthFile(file, stateDir);
62
+ if (!existsSync(path))
63
+ return {};
64
+ try {
65
+ const parsed = JSON.parse(readFileSync(path, "utf8"));
66
+ return parsed && typeof parsed === "object" ? parsed : {};
67
+ }
68
+ catch {
69
+ return {};
70
+ }
71
+ }
72
+ export function saveAuthMap(map, file, stateDir) {
73
+ const path = resolveAuthFile(file, stateDir);
74
+ mkdirSync(dirname(path), { recursive: true });
75
+ // Mode 0600 — the file holds long-lived refresh tokens.
76
+ writeFileSync(path, JSON.stringify(map, null, 2), { mode: 0o600 });
77
+ }
78
+ export function hasOAuthCredentials(id, file, stateDir) {
79
+ return !!loadAuthMap(file, stateDir)[id];
80
+ }
81
+ /**
82
+ * Resolve a usable API key for an OAuth provider from stored credentials,
83
+ * refreshing an expired token and persisting the rotated credentials back to
84
+ * the store. Returns null when nothing is stored for `id`. Throws only if a
85
+ * refresh actually fails (expired refresh token, revoked grant) — callers
86
+ * should surface that as "re-run login".
87
+ */
88
+ export async function resolveOAuthApiKey(id, file, stateDir) {
89
+ const map = loadAuthMap(file, stateDir);
90
+ if (!map[id])
91
+ return null;
92
+ const res = await getOAuthApiKey(id, map);
93
+ if (!res)
94
+ return null;
95
+ // Persist the rotated credentials so the next refresh chains from the new
96
+ // token rather than re-using a spent one.
97
+ map[id] = { type: "oauth", ...res.newCredentials };
98
+ saveAuthMap(map, file, stateDir);
99
+ return { apiKey: res.apiKey, credentials: res.newCredentials };
100
+ }
101
+ /** Re-exported so callers depend on this module, not pi-ai's oauth entry directly. */
102
+ export { getOAuthProvider, getOAuthProviders };
103
+ //# sourceMappingURL=oauth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oauth.js","sourceRoot":"","sources":["../src/oauth.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ export type OverlayAssetType = "workflow" | "cron" | "prompt" | "skill" | "agent-context";
2
+ export interface OverlayAsset {
3
+ type: OverlayAssetType;
4
+ /** Logical name — workflow/cron/skill name, or the filename for prompts/context. */
5
+ name: string;
6
+ /** True when a same-named built-in exists under `coreRoot` (overlay shadows it);
7
+ * false when the overlay adds something the built-ins don't have. */
8
+ shadowsDefault: boolean;
9
+ }
10
+ export interface EnumerateOpts {
11
+ /** Built-in asset tree (the lastlight checkout root). */
12
+ coreRoot?: string;
13
+ /** Overlay asset tree (the instance/ overlay root). */
14
+ overlayRoot?: string;
15
+ }
16
+ /**
17
+ * List every asset the overlay provides, tagging each as shadowing a built-in or
18
+ * a fresh addition. Returns `[]` when no overlay is configured or present.
19
+ */
20
+ export declare function enumerateOverlayAssets(opts: EnumerateOpts): OverlayAsset[];
@@ -0,0 +1,114 @@
1
+ /**
2
+ * Enumerate the assets a deployment overlay overrides or adds.
3
+ *
4
+ * The instance overlay (`$LASTLIGHT_OVERLAY_DIR`, prod `instance/`) can replace
5
+ * or add workflows, prompts, skills, and agent-context files by logical name —
6
+ * the layer-aware resolution lives in `src/workflows/loader.ts`. This module is
7
+ * the read-only inverse: given the built-in (`coreRoot`) and overlay
8
+ * (`overlayRoot`) trees, list every asset the overlay provides and whether it
9
+ * **shadows** a same-named built-in or is a fresh **addition**.
10
+ *
11
+ * It's a pure filesystem walk (no loader caches), so the host-local CLI
12
+ * (`lastlight server status`, `lastlight fork`) and the in-harness admin API
13
+ * (`GET /admin/api/overrides`) can share one source of truth.
14
+ */
15
+ import { readFileSync, readdirSync, statSync } from "node:fs";
16
+ import { join } from "node:path";
17
+ import { parse as parseYaml } from "yaml";
18
+ function isDir(path) {
19
+ try {
20
+ return statSync(path).isDirectory();
21
+ }
22
+ catch {
23
+ return false;
24
+ }
25
+ }
26
+ function listFiles(dir, predicate) {
27
+ if (!isDir(dir))
28
+ return [];
29
+ return readdirSync(dir).filter(predicate).sort();
30
+ }
31
+ function listDirs(dir) {
32
+ if (!isDir(dir))
33
+ return [];
34
+ return readdirSync(dir)
35
+ .filter((n) => isDir(join(dir, n)))
36
+ .sort();
37
+ }
38
+ const isYaml = (n) => n.endsWith(".yaml") || n.endsWith(".yml");
39
+ const isMarkdown = (n) => n.endsWith(".md");
40
+ /** Read a workflow YAML's `name`/`kind` without throwing on malformed files. */
41
+ function readWorkflowMeta(filePath) {
42
+ try {
43
+ const raw = parseYaml(readFileSync(filePath, "utf-8"));
44
+ if (!raw || typeof raw.name !== "string")
45
+ return null;
46
+ return { name: raw.name, kind: typeof raw.kind === "string" ? raw.kind : "" };
47
+ }
48
+ catch {
49
+ return null;
50
+ }
51
+ }
52
+ /**
53
+ * Index built-in assets by logical name so overlay entries can be tagged as
54
+ * shadowing vs adding. Workflows/crons are keyed by their YAML `name`; prompts,
55
+ * skills, and agent-context by filename / directory name.
56
+ */
57
+ function indexBuiltins(coreRoot) {
58
+ const workflows = new Set();
59
+ const crons = new Set();
60
+ const workflowDir = join(coreRoot, "workflows");
61
+ for (const file of listFiles(workflowDir, isYaml)) {
62
+ const meta = readWorkflowMeta(join(workflowDir, file));
63
+ if (!meta)
64
+ continue;
65
+ (meta.kind === "cron" ? crons : workflows).add(meta.name);
66
+ }
67
+ return {
68
+ workflows,
69
+ crons,
70
+ prompts: new Set(listFiles(join(coreRoot, "workflows", "prompts"), isMarkdown)),
71
+ skills: new Set(listDirs(join(coreRoot, "skills"))),
72
+ agentContext: new Set(listFiles(join(coreRoot, "agent-context"), isMarkdown)),
73
+ };
74
+ }
75
+ /**
76
+ * List every asset the overlay provides, tagging each as shadowing a built-in or
77
+ * a fresh addition. Returns `[]` when no overlay is configured or present.
78
+ */
79
+ export function enumerateOverlayAssets(opts) {
80
+ const { coreRoot, overlayRoot } = opts;
81
+ if (!overlayRoot || !isDir(overlayRoot))
82
+ return [];
83
+ const builtins = coreRoot && isDir(coreRoot)
84
+ ? indexBuiltins(coreRoot)
85
+ : { workflows: new Set(), crons: new Set(), prompts: new Set(), skills: new Set(), agentContext: new Set() };
86
+ const assets = [];
87
+ // Workflows + crons — keyed by YAML `name`, split by `kind`.
88
+ const overlayWorkflowDir = join(overlayRoot, "workflows");
89
+ for (const file of listFiles(overlayWorkflowDir, isYaml)) {
90
+ const meta = readWorkflowMeta(join(overlayWorkflowDir, file));
91
+ if (!meta)
92
+ continue;
93
+ const isCron = meta.kind === "cron";
94
+ assets.push({
95
+ type: isCron ? "cron" : "workflow",
96
+ name: meta.name,
97
+ shadowsDefault: (isCron ? builtins.crons : builtins.workflows).has(meta.name),
98
+ });
99
+ }
100
+ // Prompts — keyed by filename under workflows/prompts/.
101
+ for (const file of listFiles(join(overlayRoot, "workflows", "prompts"), isMarkdown)) {
102
+ assets.push({ type: "prompt", name: file, shadowsDefault: builtins.prompts.has(file) });
103
+ }
104
+ // Skills — keyed by directory name under skills/.
105
+ for (const dir of listDirs(join(overlayRoot, "skills"))) {
106
+ assets.push({ type: "skill", name: dir, shadowsDefault: builtins.skills.has(dir) });
107
+ }
108
+ // Agent-context — keyed by filename under agent-context/.
109
+ for (const file of listFiles(join(overlayRoot, "agent-context"), isMarkdown)) {
110
+ assets.push({ type: "agent-context", name: file, shadowsDefault: builtins.agentContext.has(file) });
111
+ }
112
+ return assets;
113
+ }
114
+ //# sourceMappingURL=overlay-assets.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"overlay-assets.js","sourceRoot":"","sources":["../src/overlay-assets.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,44 @@
1
+ /** `.gitignore` for the overlay so it can be its own private repo without
2
+ * leaking secrets. Everything under `secrets/` is ignored except the template. */
3
+ export declare const OVERLAY_GITIGNORE: string;
4
+ /** Placeholder `config.yaml` for a fresh overlay (no managed repos yet). */
5
+ export declare const OVERLAY_CONFIG_PLACEHOLDER: string;
6
+ /** Template `secrets/.env.example` — the only tracked file under `secrets/`.
7
+ * Mirrors the keys `npx lastlight setup` writes into `secrets/.env`. */
8
+ export declare const OVERLAY_ENV_EXAMPLE: string;
9
+ /** README dropped into a freshly scaffolded overlay. */
10
+ export declare const OVERLAY_README: string;
11
+ export interface GhStatus {
12
+ /** `gh` is on PATH. */
13
+ installed: boolean;
14
+ /** `gh auth status` succeeded — we can create repos. */
15
+ authed: boolean;
16
+ /** Authenticated GitHub login, when resolvable. */
17
+ login?: string;
18
+ }
19
+ /** Probe the GitHub CLI. Never throws — a missing/unauthed `gh` resolves to a
20
+ * status the callers fall back from (print manual commands instead). */
21
+ export declare function detectGh(): Promise<GhStatus>;
22
+ export interface ScaffoldResult {
23
+ /** Repo-relative paths of files this call created (existing files untouched). */
24
+ created: string[];
25
+ }
26
+ /**
27
+ * Write the default overlay files into `instanceDir`, creating only what's
28
+ * missing — never overwriting an existing file. Returns the relative paths
29
+ * created so callers can report them.
30
+ */
31
+ export declare function scaffoldOverlayFiles(instanceDir: string): ScaffoldResult;
32
+ export interface BootstrapOpts {
33
+ /** Pre-probed gh status (call {@link detectGh} once and pass it in). */
34
+ gh: GhStatus;
35
+ /** Non-interactive — skip prompts, just print the manual commands. */
36
+ yes?: boolean;
37
+ }
38
+ /**
39
+ * Version `instanceDir` as a private git repo and (optionally) create + push a
40
+ * GitHub repo for it. Idempotent: skips `git init` when already a repo, and
41
+ * skips repo creation when an `origin` remote already exists. Falls back to
42
+ * printing the manual commands whenever `gh` is unavailable or the user declines.
43
+ */
44
+ export declare function bootstrapOverlayRepo(instanceDir: string, opts: BootstrapOpts): Promise<void>;