@crouter/api 0.3.387 → 0.3.388

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.
Files changed (130) hide show
  1. package/dist/api/__tests__/integration/client.test.js +97 -0
  2. package/dist/api/client.d.ts +7 -0
  3. package/dist/api/client.js +40 -21
  4. package/dist/core/asset-root.d.ts +7 -0
  5. package/dist/core/asset-root.js +18 -0
  6. package/dist/core/canvas/boot-id.d.ts +6 -0
  7. package/dist/core/canvas/boot-id.js +26 -0
  8. package/dist/core/canvas/paths.d.ts +72 -0
  9. package/dist/core/canvas/paths.js +163 -0
  10. package/dist/core/canvas/pid.d.ts +391 -0
  11. package/dist/core/canvas/pid.js +948 -0
  12. package/dist/core/command-plugins/bundle.d.ts +149 -0
  13. package/dist/core/command-plugins/bundle.js +588 -0
  14. package/dist/core/command-plugins/endpoint.d.ts +24 -0
  15. package/dist/core/command-plugins/endpoint.js +51 -0
  16. package/dist/core/config.d.ts +233 -0
  17. package/dist/core/config.js +1120 -0
  18. package/dist/core/env-name.d.ts +6 -0
  19. package/dist/core/env-name.js +9 -0
  20. package/dist/core/errors.d.ts +38 -0
  21. package/dist/core/errors.js +90 -0
  22. package/dist/core/events/emit.d.ts +6 -0
  23. package/dist/core/events/emit.js +42 -0
  24. package/dist/core/events/envelope.d.ts +2 -0
  25. package/dist/core/events/envelope.js +84 -0
  26. package/dist/core/events/errors.d.ts +4 -0
  27. package/dist/core/events/errors.js +69 -0
  28. package/dist/core/events/operation-id.d.ts +4 -0
  29. package/dist/core/events/operation-id.js +24 -0
  30. package/dist/core/events/serialize.d.ts +4 -0
  31. package/dist/core/events/serialize.js +199 -0
  32. package/dist/core/events/source.d.ts +16 -0
  33. package/dist/core/events/source.js +31 -0
  34. package/dist/core/events/types.d.ts +68 -0
  35. package/dist/core/events/types.js +11 -0
  36. package/dist/core/exclusive-lock.d.ts +34 -0
  37. package/dist/core/exclusive-lock.js +197 -0
  38. package/dist/core/fs-utils.d.ts +44 -0
  39. package/dist/core/fs-utils.js +208 -0
  40. package/dist/core/help.d.ts +309 -0
  41. package/dist/core/help.js +406 -0
  42. package/dist/core/human/page-catalog.d.ts +57 -0
  43. package/dist/core/human/page-catalog.js +172 -0
  44. package/dist/core/installed-plugins.d.ts +2 -0
  45. package/dist/core/installed-plugins.js +79 -0
  46. package/dist/core/io.d.ts +122 -0
  47. package/dist/core/io.js +373 -0
  48. package/dist/core/keybindings/attach-control.d.ts +49 -0
  49. package/dist/core/keybindings/attach-control.js +42 -0
  50. package/dist/core/keybindings/catalog.d.ts +18 -0
  51. package/dist/core/keybindings/catalog.js +257 -0
  52. package/dist/core/keybindings/types.d.ts +42 -0
  53. package/dist/core/keybindings/types.js +1 -0
  54. package/dist/core/layout.d.ts +26 -0
  55. package/dist/core/layout.js +94 -0
  56. package/dist/core/locked-file.d.ts +27 -0
  57. package/dist/core/locked-file.js +118 -0
  58. package/dist/core/log.d.ts +9 -0
  59. package/dist/core/log.js +89 -0
  60. package/dist/core/manifest.d.ts +5 -0
  61. package/dist/core/manifest.js +15 -0
  62. package/dist/core/plugin-env.d.ts +8 -0
  63. package/dist/core/plugin-env.js +31 -0
  64. package/dist/core/plugin-extensions.d.ts +29 -0
  65. package/dist/core/plugin-extensions.js +191 -0
  66. package/dist/core/plugin-swap-lock.d.ts +9 -0
  67. package/dist/core/plugin-swap-lock.js +31 -0
  68. package/dist/core/preview-result-path.d.ts +4 -0
  69. package/dist/core/preview-result-path.js +26 -0
  70. package/dist/core/profiles/env-store.d.ts +22 -0
  71. package/dist/core/profiles/env-store.js +163 -0
  72. package/dist/core/profiles/fuzzy-match.d.ts +19 -0
  73. package/dist/core/profiles/fuzzy-match.js +92 -0
  74. package/dist/core/profiles/manifest.d.ts +120 -0
  75. package/dist/core/profiles/manifest.js +529 -0
  76. package/dist/core/rate-limit-scope.d.ts +25 -0
  77. package/dist/core/rate-limit-scope.js +64 -0
  78. package/dist/core/render.d.ts +12 -0
  79. package/dist/core/render.js +138 -0
  80. package/dist/core/resolver.d.ts +14 -0
  81. package/dist/core/resolver.js +111 -0
  82. package/dist/core/runtime/branded-host.d.ts +25 -0
  83. package/dist/core/runtime/branded-host.js +264 -0
  84. package/dist/core/runtime/broker/daemon-ops.d.ts +65 -0
  85. package/dist/core/runtime/broker/daemon-ops.js +177 -0
  86. package/dist/core/runtime/broker/signal-stream.d.ts +30 -0
  87. package/dist/core/runtime/broker/signal-stream.js +149 -0
  88. package/dist/core/scope.d.ts +32 -0
  89. package/dist/core/scope.js +184 -0
  90. package/dist/core/scoped-state/db.d.ts +17 -0
  91. package/dist/core/scoped-state/db.js +247 -0
  92. package/dist/core/scoped-state/migrate.d.ts +8 -0
  93. package/dist/core/scoped-state/migrate.js +187 -0
  94. package/dist/core/scoped-state/paths.d.ts +9 -0
  95. package/dist/core/scoped-state/paths.js +27 -0
  96. package/dist/core/scoped-state/profiles.d.ts +27 -0
  97. package/dist/core/scoped-state/profiles.js +93 -0
  98. package/dist/core/scoped-state/providers.d.ts +24 -0
  99. package/dist/core/scoped-state/providers.js +19 -0
  100. package/dist/core/scoped-state/schema.d.ts +6 -0
  101. package/dist/core/scoped-state/schema.js +43 -0
  102. package/dist/core/scoped-state/settings.d.ts +28 -0
  103. package/dist/core/scoped-state/settings.js +83 -0
  104. package/dist/core/spaces/open-beneath.d.ts +71 -0
  105. package/dist/core/spaces/open-beneath.js +581 -0
  106. package/dist/core/sqlite-statements.d.ts +4 -0
  107. package/dist/core/sqlite-statements.js +17 -0
  108. package/dist/core/subscription-state.d.ts +121 -0
  109. package/dist/core/subscription-state.js +287 -0
  110. package/dist/core/user-settings.d.ts +377 -0
  111. package/dist/core/user-settings.js +458 -0
  112. package/dist/daemon/broker-signals/bus.d.ts +30 -0
  113. package/dist/daemon/broker-signals/bus.js +87 -0
  114. package/dist/daemon/manage.d.ts +176 -0
  115. package/dist/daemon/manage.js +664 -0
  116. package/dist/daemon/pidfile.d.ts +8 -0
  117. package/dist/daemon/pidfile.js +37 -0
  118. package/dist/daemon/startup-policy.d.ts +1 -0
  119. package/dist/daemon/startup-policy.js +1 -0
  120. package/dist/native/linux.d.ts +29 -0
  121. package/dist/native/linux.js +20 -0
  122. package/dist/shared/env.d.ts +116 -0
  123. package/dist/shared/env.js +271 -0
  124. package/dist/shared/inbox-entry-body.d.ts +22 -0
  125. package/dist/shared/inbox-entry-body.js +116 -0
  126. package/dist/shared/working-activity.d.ts +9 -0
  127. package/dist/shared/working-activity.js +27 -0
  128. package/dist/types.d.ts +562 -0
  129. package/dist/types.js +186 -0
  130. package/package.json +1 -1
@@ -0,0 +1,163 @@
1
+ // The profile-owned env store: `<runtime>/secrets/env/<app>/<profileId>` — a 0600
2
+ // map of env-var name -> value a profile owns explicitly. `buildBrokerEnv`
3
+ // (`core/runtime/spawn-env.ts`) reads it straight off disk and injects the
4
+ // values directly into every broker launched under that profile, independent
5
+ // of the daemon's own ambient `process.env` or any `.envrc`/`.env` a node's
6
+ // cwd might carry. Setting a value here IS the consent to cross the spawn
7
+ // boundary — no matching `spawnEnv.allow` entry is needed, because these
8
+ // values never pass through the host-env allowlist in the first place; they
9
+ // are a separate, always-injected source. Never surface a stored VALUE
10
+ // outside this file's own readers — `listProfileEnvNames` returns names only,
11
+ // and no caller should log or echo what `readProfileEnvVars` returns.
12
+ import { existsSync, readFileSync, renameSync, writeFileSync, chmodSync } from 'node:fs';
13
+ import { dirname } from 'node:path';
14
+ import { profileSpace } from '../layout.js';
15
+ import { loadExactProfileManifest, withProfileManifestLock } from './manifest.js';
16
+ import { ensureDir } from '../fs-utils.js';
17
+ import { notFound, usage } from '../errors.js';
18
+ /** crtr's own identity/routing env names — reserved so a stored profile value
19
+ * can never redirect a broker to a different canvas home/db/socket dir or
20
+ * masquerade as its own process identity. `HOME` is reserved alongside the
21
+ * `CRTR_*` family because crtr has no independent authoritative scope root
22
+ * yet; every path in `core/canvas/paths.ts` ultimately derives from it.
23
+ * Enforced at write (`setProfileEnvVar` throws below) AND defensively at
24
+ * read (`readStoreUnlocked`, which every reader — list/set/remove/inject —
25
+ * goes through), so even a reserved name that reached disk some other way
26
+ * (hand-edited file, a pre-reservation write) is uniformly invisible rather
27
+ * than trusted. */
28
+ const RESERVED_ENV_PREFIX = 'CRTR_';
29
+ const RESERVED_ENV_EXACT_NAMES = new Set(['HOME']);
30
+ export function isReservedEnvName(name) {
31
+ return name.startsWith(RESERVED_ENV_PREFIX) || RESERVED_ENV_EXACT_NAMES.has(name);
32
+ }
33
+ /** NUL is the one byte no OS accepts in an env value — Node's own `spawn`
34
+ * rejects it synchronously and echoes the full value in the thrown error, so
35
+ * it must never reach storage or a spawn call. */
36
+ function hasNulByte(value) {
37
+ return value.includes('\0');
38
+ }
39
+ function envPath(profileId) {
40
+ const profile = loadExactProfileManifest(profileId);
41
+ return profileSpace(profile.manifest.grantee ?? 'app:terminal', profileId).env;
42
+ }
43
+ /** Reads env.json into a null-prototype, sanitized own-property map.
44
+ * Null-prototype so a stored name like `__proto__` is a plain own data
45
+ * property instead of silently colliding with the inherited accessor on an
46
+ * ordinary object (which would make `in`/assignment lie about what's
47
+ * actually stored — `created`/`removed` would misreport, and the value
48
+ * would never actually persist). Sanitized — dropping any non-string,
49
+ * NUL-containing, or reserved-name entry — because this file may have been
50
+ * hand-edited or written by a version that predates these guards, and every
51
+ * reader (list/set/remove/inject) shares this one function, so a bad
52
+ * on-disk entry is uniformly invisible instead of leaking into one path but
53
+ * not another. Never throws: a corrupt file degrades to empty, matching the
54
+ * prior behavior. */
55
+ function readStoreUnlocked(profileId) {
56
+ const p = envPath(profileId);
57
+ const vars = Object.create(null);
58
+ if (!existsSync(p))
59
+ return { vars };
60
+ try {
61
+ const parsed = JSON.parse(readFileSync(p, 'utf8'));
62
+ for (const [name, value] of Object.entries(parsed.vars ?? {})) {
63
+ if (typeof value !== 'string')
64
+ continue;
65
+ if (hasNulByte(value))
66
+ continue;
67
+ if (isReservedEnvName(name))
68
+ continue;
69
+ vars[name] = value;
70
+ }
71
+ }
72
+ catch {
73
+ // Corrupt/unparseable file: degrade to whatever was sanitized so far
74
+ // (empty, since the loop above never ran).
75
+ }
76
+ return { vars };
77
+ }
78
+ /** Writes env.json directly (never via `fs-utils`' generic writers, which
79
+ * have no mode control) so the file is created 0600 from the first byte,
80
+ * then `chmodSync`s belt-and-suspenders in case it pre-existed with looser
81
+ * permissions — the same shape as `core/secrets.ts`. Atomic via tmp+rename. */
82
+ function writeStoreAtomic(profileId, store) {
83
+ const p = envPath(profileId);
84
+ ensureDir(dirname(p));
85
+ const tmp = `${p}.tmp.${process.pid}.${Math.random().toString(36).slice(2)}`;
86
+ writeFileSync(tmp, JSON.stringify(store, null, 2) + '\n', { mode: 0o600 });
87
+ chmodSync(tmp, 0o600);
88
+ renameSync(tmp, p);
89
+ }
90
+ function requireProfileExists(profileId) {
91
+ try {
92
+ loadExactProfileManifest(profileId);
93
+ }
94
+ catch (error) {
95
+ if (error instanceof Error && error.message.startsWith('profile not found:'))
96
+ throw notFound(`profile not found: ${profileId}`, { received: profileId });
97
+ throw error;
98
+ }
99
+ }
100
+ /** Every stored variable NAME for a profile, sorted. Never returns values. */
101
+ export function listProfileEnvNames(profileId) {
102
+ return Object.keys(readStoreUnlocked(profileId).vars).sort();
103
+ }
104
+ /** Set one variable, replacing any prior value under the same name. Holds the
105
+ * same per-profile lock manifest mutations use (`withProfileManifestLock`),
106
+ * so a concurrent `profile delete` can never race a write into an
107
+ * about-to-vanish profile dir — whichever runs first wins the lock, and the
108
+ * loser either sees `not_found` (delete-then-set) or removes the file right
109
+ * back out from under a just-written value (set-then-delete, via
110
+ * `deleteProfile`'s recursive rm of the whole profile root). Returns whether
111
+ * the name was newly added (`true`) or replaced an existing value (`false`). */
112
+ export function setProfileEnvVar(profileId, name, value) {
113
+ if (isReservedEnvName(name)) {
114
+ throw usage(`env var name is reserved by crtr: ${name}`, {
115
+ received: name,
116
+ field: 'name',
117
+ next: 'crtr identity/routing names (CRTR_* and HOME) cannot be stored in a profile env store — a stored value under one of these names could redirect a broker to a different canvas home. Choose a different name.',
118
+ });
119
+ }
120
+ if (hasNulByte(value)) {
121
+ throw usage('env var value contains a NUL byte, which no OS accepts in an environment variable', {
122
+ field: 'value',
123
+ next: 'Remove NUL bytes from the piped value and retry.',
124
+ });
125
+ }
126
+ return withProfileManifestLock(profileId, () => {
127
+ requireProfileExists(profileId);
128
+ const store = readStoreUnlocked(profileId);
129
+ const created = !Object.hasOwn(store.vars, name);
130
+ store.vars[name] = value;
131
+ writeStoreAtomic(profileId, store);
132
+ return created;
133
+ });
134
+ }
135
+ /** Remove one variable. Returns whether it was present. */
136
+ export function removeProfileEnvVar(profileId, name) {
137
+ return withProfileManifestLock(profileId, () => {
138
+ requireProfileExists(profileId);
139
+ const store = readStoreUnlocked(profileId);
140
+ if (!Object.hasOwn(store.vars, name))
141
+ return false;
142
+ delete store.vars[name];
143
+ writeStoreAtomic(profileId, store);
144
+ return true;
145
+ });
146
+ }
147
+ /** Every stored `{name: value}` for a profile — the ONLY function that
148
+ * returns raw values, called exclusively by `buildBrokerEnv`
149
+ * (`core/runtime/spawn-env.ts`) to inject them directly into a launched
150
+ * broker's env. Never throws: a stale/invalid/deleted profile id is a
151
+ * hot-path no-op (mirrors `readRawProfileConfig` in `core/config.ts`),
152
+ * because broker-env resolution must never fail a launch over a bad profile
153
+ * id. */
154
+ export function readProfileEnvVars(profileId) {
155
+ if (profileId === null || profileId === '')
156
+ return {};
157
+ try {
158
+ return { ...readStoreUnlocked(profileId).vars };
159
+ }
160
+ catch {
161
+ return {};
162
+ }
163
+ }
@@ -0,0 +1,19 @@
1
+ /** One candidate offered to the matcher: the durable id plus the mutable name. */
2
+ export interface FuzzyCandidate {
3
+ readonly profileId: string;
4
+ readonly name: string;
5
+ }
6
+ export interface FuzzyResult {
7
+ /** The single profile the needle resolved to, or `null` when nothing was
8
+ * close enough or several were equally close. */
9
+ readonly match: FuzzyCandidate | null;
10
+ /** Populated only when several profiles tied — the caller reports these so
11
+ * the user can disambiguate without running `profile list`. */
12
+ readonly tied: readonly FuzzyCandidate[];
13
+ }
14
+ /** Resolve a typed operand against the profiles on this host. Returns a single
15
+ * match only when it is unambiguously the best one: the strongest tier any
16
+ * candidate reached, and within it the single lowest edit distance. A genuine
17
+ * tie is reported rather than guessed, because silently running under the
18
+ * wrong profile is worse than one more prompt. */
19
+ export declare function fuzzyMatchProfile(operand: string, candidates: readonly FuzzyCandidate[]): FuzzyResult;
@@ -0,0 +1,92 @@
1
+ // Fuzzy matching for a user-TYPED profile operand. Pure — no fs, no manifest
2
+ // reads — so the resolution policy is testable on its own and `manifest.ts`
3
+ // stays the sole owner of path safety and manifest IO.
4
+ //
5
+ // This exists because nobody remembers a profile's exact registered name, let
6
+ // alone its `<slug>-<8hex>` id. `crtr --profile crout` should run the `crouter`
7
+ // profile instead of failing with a list the user then has to read and retype.
8
+ /** Case-, space-, and punctuation-insensitive comparison key. "My Profile",
9
+ * "my-profile", and "myprofile" all collapse to the same key, so the common
10
+ * near-miss (wrong separator, wrong case) resolves in the first tier. */
11
+ function normalize(value) {
12
+ return value.toLowerCase().replace(/[^a-z0-9]/g, '');
13
+ }
14
+ /** The generated id's `-<8 hex>` suffix carries no meaning to a human typing a
15
+ * profile, so match against the slug alone as well as the whole id. */
16
+ function idSlug(profileId) {
17
+ return profileId.replace(/-[0-9a-f]{8}$/, '');
18
+ }
19
+ /** Every string a human might plausibly have been aiming at for one profile. */
20
+ function haystacks(candidate) {
21
+ const keys = [normalize(candidate.name), normalize(idSlug(candidate.profileId)), normalize(candidate.profileId)];
22
+ return keys.filter((key, index) => key !== '' && keys.indexOf(key) === index);
23
+ }
24
+ function levenshtein(a, b) {
25
+ if (a === b)
26
+ return 0;
27
+ if (a.length === 0)
28
+ return b.length;
29
+ if (b.length === 0)
30
+ return a.length;
31
+ let previous = Array.from({ length: b.length + 1 }, (_, i) => i);
32
+ for (let i = 1; i <= a.length; i += 1) {
33
+ const current = [i];
34
+ for (let j = 1; j <= b.length; j += 1) {
35
+ const substitution = previous[j - 1] + (a[i - 1] === b[j - 1] ? 0 : 1);
36
+ current[j] = Math.min(current[j - 1] + 1, previous[j] + 1, substitution);
37
+ }
38
+ previous = current;
39
+ }
40
+ return previous[b.length];
41
+ }
42
+ /** Tiers, strongest first. They are ordered by how confidently a human meant
43
+ * this profile, NOT by string similarity — a prefix the user stopped typing is
44
+ * a stronger signal than a one-character edit that happens to score well. */
45
+ const TIER_NORMALIZED_EQUAL = 0;
46
+ const TIER_PREFIX = 1;
47
+ const TIER_CONTAINS = 2;
48
+ const TIER_TYPO = 3;
49
+ function scoreOne(needle, candidate) {
50
+ let best = null;
51
+ for (const hay of haystacks(candidate)) {
52
+ const distance = levenshtein(needle, hay);
53
+ let tier = null;
54
+ if (needle === hay)
55
+ tier = TIER_NORMALIZED_EQUAL;
56
+ else if (hay.startsWith(needle))
57
+ tier = TIER_PREFIX;
58
+ else if (hay.includes(needle) || needle.includes(hay))
59
+ tier = TIER_CONTAINS;
60
+ // A typo budget that scales with length: one edit for a short name, more
61
+ // for a long one. Without the floor, a 3-character name would tolerate
62
+ // nothing; without the scaling, two unrelated long names would collide.
63
+ else if (distance <= Math.max(1, Math.floor(hay.length / 3)))
64
+ tier = TIER_TYPO;
65
+ if (tier === null)
66
+ continue;
67
+ if (best === null || tier < best.tier || (tier === best.tier && distance < best.distance)) {
68
+ best = { candidate, tier, distance };
69
+ }
70
+ }
71
+ return best;
72
+ }
73
+ /** Resolve a typed operand against the profiles on this host. Returns a single
74
+ * match only when it is unambiguously the best one: the strongest tier any
75
+ * candidate reached, and within it the single lowest edit distance. A genuine
76
+ * tie is reported rather than guessed, because silently running under the
77
+ * wrong profile is worse than one more prompt. */
78
+ export function fuzzyMatchProfile(operand, candidates) {
79
+ const needle = normalize(operand);
80
+ if (needle === '')
81
+ return { match: null, tied: [] };
82
+ const scored = candidates.map((c) => scoreOne(needle, c)).filter((s) => s !== null);
83
+ if (scored.length === 0)
84
+ return { match: null, tied: [] };
85
+ const bestTier = Math.min(...scored.map((s) => s.tier));
86
+ const inTier = scored.filter((s) => s.tier === bestTier);
87
+ const bestDistance = Math.min(...inTier.map((s) => s.distance));
88
+ const winners = inTier.filter((s) => s.distance === bestDistance);
89
+ if (winners.length === 1)
90
+ return { match: winners[0].candidate, tied: [] };
91
+ return { match: null, tied: winners.map((s) => s.candidate) };
92
+ }
@@ -0,0 +1,120 @@
1
+ import type { ProfileManifest } from '../../types.js';
2
+ import type { DeliveryLimits, ProfileProject, ProfileProjectMemory } from '../../api/dto/profiles.js';
3
+ export declare const ROOT_PROFILE_ID = "root-00000000";
4
+ export declare function profileMemoryDir(profileId: string): string;
5
+ export declare function assertProfileProjects(entries: unknown): asserts entries is ProfileProject[];
6
+ export declare function assertDeliveryLimits(value: unknown): asserts value is DeliveryLimits;
7
+ export interface ProfileEntry {
8
+ profileId: string;
9
+ manifest: ProfileManifest;
10
+ }
11
+ export interface ProfileEnumeration {
12
+ entries: ProfileEntry[];
13
+ unreadableIds: string[];
14
+ }
15
+ export declare function listProfiles(): ProfileEntry[];
16
+ export type ProfileLookup = {
17
+ kind: 'resolved';
18
+ entry: ProfileEntry;
19
+ } | {
20
+ kind: 'absent';
21
+ } | {
22
+ kind: 'unreadable';
23
+ profileId: string;
24
+ manifestPath: string;
25
+ };
26
+ export declare function lookupProfile(profileIdOrName: string): ProfileLookup;
27
+ /** Read one exact profile id without falling back to manifest-name matching.
28
+ * Used at persistence gates that already hold a resolved durable identity. */
29
+ export declare function loadExactProfileManifest(profileId: string): ProfileEntry;
30
+ /** Exact resolution only: the generated id, else a unique manifest `name`.
31
+ * Returns `null` when nothing matches exactly; still THROWS when a name is
32
+ * shared by several profiles, because that is a real collision the caller
33
+ * cannot resolve by guessing (and fuzzy matching would only guess harder). */
34
+ /** The two facts operand matching reads, from a stored entry or a daemon DTO. */
35
+ export interface ProfileCandidate {
36
+ profileId: string;
37
+ name: string;
38
+ }
39
+ type CandidateOf<T> = (entry: T) => ProfileCandidate;
40
+ export declare function loadProfileManifest(profileIdOrName: string): ProfileEntry;
41
+ /** Resolve a profile the USER TYPED — a `--profile` flag or a `<profile>`
42
+ * operand. Exact id or name wins outright; only when nothing matches exactly
43
+ * does this fall back to fuzzy matching on name and id slug, so a typed
44
+ * `crout`, `Crouter`, or `cruoter` reaches the `crouter` profile instead of
45
+ * failing with a list the user has to read and retype. A fuzzy hit is
46
+ * announced on STDERR (never stdout, which callers pipe) so the user always
47
+ * knows which profile actually ran.
48
+ *
49
+ * NEVER call this with a STORED `profile_id` (a node's `meta.profile_id`,
50
+ * `CRTR_PROFILE_ID`, a pinned default). A durable id that no longer resolves
51
+ * means the profile was deleted — the correct answer there is the not-found
52
+ * those callers already handle, not the nearest surviving profile. Use
53
+ * `loadProfileManifest` (or `loadExactProfileManifest`) for those. */
54
+ export declare function resolveProfileOperand(operand: string): ProfileEntry;
55
+ /** `resolveProfileOperand` over any candidate list — the CLI passes the
56
+ * profiles the daemon lists for its caller. */
57
+ export declare function matchProfileOperand<T>(all: readonly T[], operand: string, key: CandidateOf<T>): T;
58
+ /** Hold the per-profile manifest lock for the duration of `fn`. ALL manifest
59
+ * mutations (create/rename/add-project/remove-project/delete/last-used)
60
+ * below run inside this. */
61
+ export declare function withProfileManifestLock<T>(profileId: string, fn: () => T): T;
62
+ export declare function ensureRootProfile(): ProfileEntry;
63
+ /** The env name a metadata entry surfaces as (spawn-env source F). Also the
64
+ * uniqueness domain for keys: the fold is lossy (`a-b`, `a_b`, `A_B` →
65
+ * `CRTR_PROFILE_META_A_B`), so two stored keys may not share a fold. */
66
+ export declare function metadataEnvName(key: string): string;
67
+ /** Reject anything a valid metadata map may not carry. Takes `unknown`
68
+ * because the API route hands over a caller-supplied body slice — the
69
+ * TypeScript type on the DTO proves nothing at runtime. */
70
+ export declare function assertProfileMetadata(entries: unknown): asserts entries is Record<string, string>;
71
+ /** Drop entries no valid mutation could have written — a manifest is
72
+ * hand-editable, so a projection or merge base must never trust it raw.
73
+ * Never throws; on an env-name collision the first entry wins (matching
74
+ * injection, where a deterministic winner beats insertion-order luck). */
75
+ export declare function sanitizeProfileMetadata(stored: unknown): Record<string, string>;
76
+ export declare function createProfile(name: string, projects?: ProfileProject[], opts?: {
77
+ defaultKind?: string;
78
+ metadata?: Record<string, string>;
79
+ grantee?: string;
80
+ }): ProfileEntry;
81
+ export declare function ensureAppProfile(grantee: string): ProfileEntry;
82
+ export declare function updateProfileLastUsed(profileId: string): ProfileEntry;
83
+ /** Paused check for an ALREADY-RESOLVED durable id (a canvas row's `profile_id`,
84
+ * `CRTR_PROFILE_ID`) — exact-id only, no name matching, so it costs one small
85
+ * read on the hot delivery paths that call it every poll. Fails OPEN: an id
86
+ * whose manifest is missing or corrupt is not paused, because treating it as
87
+ * paused would strand that node's queued inbox forever. */
88
+ export declare function isProfilePaused(profileId: string | null | undefined): boolean;
89
+ /** One set-based pause read for canvas-wide projections. */
90
+ export declare function pausedCanvasProfiles(): Set<string>;
91
+ /** Refuse a launch under a paused profile, naming the resume command. Shared by
92
+ * every gate that already holds a loaded manifest. */
93
+ export declare function assertProfileEntryActive(entry: ProfileEntry): void;
94
+ export declare function assertProfileActive(profileId: string | null | undefined): ProfileEntry | null;
95
+ export declare function pauseProfile(profileId: string): ProfileEntry;
96
+ export declare function resumeProfile(profileId: string): ProfileEntry & {
97
+ pausedAt: string | null;
98
+ };
99
+ export declare function setProfileDefaultKind(profileId: string, defaultKind: string): ProfileEntry;
100
+ /** Merge `set` entries over the stored map and drop `unset` keys; the
101
+ * `metadata` field is omitted entirely when the result is empty. */
102
+ export declare function updateProfileMetadata(profileId: string, set: Record<string, string>, unset?: string[]): ProfileEntry;
103
+ /** A profile's stored metadata for broker-env injection — sanitized entry by
104
+ * entry (a manifest is hand-editable, so a bad key/value is dropped rather
105
+ * than trusted) and never throwing: a stale/invalid/absent profile id is a
106
+ * hot-path no-op, because broker-env resolution must never fail a launch
107
+ * over a bad profile id (mirrors `readProfileEnvVars`). */
108
+ export declare function readProfileMetadata(profileId: string | null): Record<string, string>;
109
+ /** Change the manifest `name` only. Callers that own the canvas (the daemon's
110
+ * `PATCH /v1/profiles/:name`) must also rename the profile's canvas handle —
111
+ * use `renameProfileAndHandle` there; the CLI and panels go through that route. */
112
+ export declare function renameProfile(profileId: string, name: string): ProfileEntry;
113
+ /** A new path appends; a path already listed keeps its position. */
114
+ export declare function addProfileProject(profileId: string, dir: string): ProfileEntry;
115
+ /** Set one owner's automatic delivery limit. `content` is the uncapped level,
116
+ * so it removes the owner's entry instead of storing it. */
117
+ export declare function setProfileDeliveryLimit(profileId: string, owner: string, level: ProfileProjectMemory): ProfileEntry;
118
+ export declare function removeProfileProject(profileId: string, dir: string): ProfileEntry;
119
+ export declare function deleteProfile(profileId: string): void;
120
+ export {};