@camstack/addon-terminal 0.1.29 → 0.1.30

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 (3) hide show
  1. package/dist/addon.js +219 -22
  2. package/dist/addon.mjs +219 -22
  3. package/package.json +1 -1
package/dist/addon.js CHANGED
@@ -20032,7 +20032,12 @@ var TerminalSessionInfoSchema = object({
20032
20032
  var TerminalProfileInfoSchema = object({
20033
20033
  profileId: string(),
20034
20034
  label: string(),
20035
- description: string().optional()
20035
+ description: string().optional(),
20036
+ /** Spawn defaults the instance form copies on create. */
20037
+ executable: string().optional(),
20038
+ args: array(string()).readonly().optional(),
20039
+ cwd: string().optional(),
20040
+ environment: array(string()).readonly().optional()
20036
20041
  });
20037
20042
  /**
20038
20043
  * A durable operator-created Terminal instance. Profiles are templates; only
@@ -20045,7 +20050,11 @@ var TerminalInstanceInfoSchema = object({
20045
20050
  profileId: string(),
20046
20051
  profileLabel: string(),
20047
20052
  name: string(),
20048
- enabled: boolean()
20053
+ enabled: boolean(),
20054
+ executable: string(),
20055
+ args: array(string()).readonly(),
20056
+ cwd: string(),
20057
+ environment: array(string()).readonly()
20049
20058
  });
20050
20059
  var TerminalLegacyCameraSchema = object({
20051
20060
  stableId: string(),
@@ -20091,7 +20100,22 @@ var terminalSessionCapability = {
20091
20100
  createInstance: method(object({
20092
20101
  targetNodeId: string().min(1),
20093
20102
  profileId: string().min(1),
20094
- name: string().trim().min(1).max(160).optional()
20103
+ name: string().trim().min(1).max(160).optional(),
20104
+ executable: string().max(1024).optional(),
20105
+ args: array(string().max(2048)).max(64).optional(),
20106
+ cwd: string().max(1024).optional(),
20107
+ environment: array(string().max(4096)).max(64).optional()
20108
+ }), TerminalInstanceInfoSchema, {
20109
+ kind: "mutation",
20110
+ auth: "admin"
20111
+ }),
20112
+ updateInstance: method(object({
20113
+ instanceId: string().min(1),
20114
+ name: string().trim().min(1).max(160).optional(),
20115
+ executable: string().max(1024).optional(),
20116
+ args: array(string().max(2048)).max(64).optional(),
20117
+ cwd: string().max(1024).optional(),
20118
+ environment: array(string().max(4096)).max(64).optional()
20095
20119
  }), TerminalInstanceInfoSchema, {
20096
20120
  kind: "mutation",
20097
20121
  auth: "admin"
@@ -20126,7 +20150,11 @@ var terminalSessionCapability = {
20126
20150
  openSession: method(object({
20127
20151
  profileId: string(),
20128
20152
  cols: number().int().positive(),
20129
- rows: number().int().positive()
20153
+ rows: number().int().positive(),
20154
+ executable: string().max(1024).optional(),
20155
+ args: array(string().max(2048)).max(64).optional(),
20156
+ cwd: string().max(1024).optional(),
20157
+ environment: array(string().max(4096)).max(64).optional()
20130
20158
  }), TerminalSessionInfoSchema, {
20131
20159
  kind: "mutation",
20132
20160
  auth: "admin"
@@ -24181,6 +24209,20 @@ var AllowedAddressesSchema = object({
24181
24209
  * Network Addresses admin page and persisted by the addon.
24182
24210
  */
24183
24211
  addresses: array(string()).readonly() });
24212
+ var TlsStatusSchema = object({
24213
+ mode: _enum([
24214
+ "generated",
24215
+ "uploaded",
24216
+ "disabled"
24217
+ ]),
24218
+ leafFingerprintSha256: string().nullable(),
24219
+ caFingerprintSha256: string().nullable(),
24220
+ validTo: string().nullable(),
24221
+ sans: array(string()),
24222
+ caCertPem: string().nullable(),
24223
+ reissueError: string().nullable(),
24224
+ restartRequired: boolean()
24225
+ });
24184
24226
  method(_void(), ListResultSchema), method(_void(), PreferredSchema), method(object({
24185
24227
  /**
24186
24228
  * LEGACY HINT — do not send from new code. Kept optional so clients
@@ -24200,7 +24242,20 @@ method(_void(), ListResultSchema), method(_void(), PreferredSchema), method(obje
24200
24242
  * to avoid mixed-content blocks in the browser. The public
24201
24243
  * tunnel always emits `https://` regardless. */
24202
24244
  scheme: _enum(["http", "https"]).optional()
24203
- }), GetConnectionEndpointsResultSchema), method(_void(), NotificationEndpointSchema), method(object({ baseUrl: string().nullable() }), NotificationEndpointSchema, { kind: "mutation" }), method(_void(), AllowedAddressesSchema), method(AllowedAddressesSchema, object({ success: literal(true) }), { kind: "mutation" }), method(_void(), AllowedAddressesSchema, { kind: "mutation" });
24245
+ }), GetConnectionEndpointsResultSchema), method(_void(), NotificationEndpointSchema), method(object({ baseUrl: string().nullable() }), NotificationEndpointSchema, { kind: "mutation" }), method(_void(), AllowedAddressesSchema), method(AllowedAddressesSchema, object({ success: literal(true) }), { kind: "mutation" }), method(_void(), AllowedAddressesSchema, { kind: "mutation" }), method(_void(), TlsStatusSchema), method(object({ reason: string().optional() }), TlsStatusSchema, {
24246
+ kind: "mutation",
24247
+ auth: "admin"
24248
+ }), method(object({
24249
+ certPem: string().min(1),
24250
+ keyPem: string().min(1),
24251
+ caPem: string().optional()
24252
+ }), TlsStatusSchema, {
24253
+ kind: "mutation",
24254
+ auth: "admin"
24255
+ }), method(_void(), object({ pem: string() })), method(_void(), TlsStatusSchema, {
24256
+ kind: "mutation",
24257
+ auth: "admin"
24258
+ });
24204
24259
  var LockControlStatusSchema = object({
24205
24260
  /** Lifecycle state of the lock. `jammed` means the motor reported
24206
24261
  * failure to reach the target — operator intervention required. */
@@ -32225,6 +32280,12 @@ Object.freeze({
32225
32280
  addonId: null,
32226
32281
  access: "create"
32227
32282
  },
32283
+ "localNetwork.downloadCa": {
32284
+ capName: "local-network",
32285
+ capScope: "system",
32286
+ addonId: null,
32287
+ access: "view"
32288
+ },
32228
32289
  "localNetwork.getAllowedAddresses": {
32229
32290
  capName: "local-network",
32230
32291
  capScope: "system",
@@ -32249,18 +32310,36 @@ Object.freeze({
32249
32310
  addonId: null,
32250
32311
  access: "view"
32251
32312
  },
32313
+ "localNetwork.getTlsStatus": {
32314
+ capName: "local-network",
32315
+ capScope: "system",
32316
+ addonId: null,
32317
+ access: "view"
32318
+ },
32252
32319
  "localNetwork.list": {
32253
32320
  capName: "local-network",
32254
32321
  capScope: "system",
32255
32322
  addonId: null,
32256
32323
  access: "view"
32257
32324
  },
32325
+ "localNetwork.regenerateCertificate": {
32326
+ capName: "local-network",
32327
+ capScope: "system",
32328
+ addonId: null,
32329
+ access: "create"
32330
+ },
32258
32331
  "localNetwork.resetAllowlistToBestMatch": {
32259
32332
  capName: "local-network",
32260
32333
  capScope: "system",
32261
32334
  addonId: null,
32262
32335
  access: "delete"
32263
32336
  },
32337
+ "localNetwork.revertToGeneratedCertificate": {
32338
+ capName: "local-network",
32339
+ capScope: "system",
32340
+ addonId: null,
32341
+ access: "create"
32342
+ },
32264
32343
  "localNetwork.setAllowedAddresses": {
32265
32344
  capName: "local-network",
32266
32345
  capScope: "system",
@@ -32273,6 +32352,12 @@ Object.freeze({
32273
32352
  addonId: null,
32274
32353
  access: "create"
32275
32354
  },
32355
+ "localNetwork.uploadCertificate": {
32356
+ capName: "local-network",
32357
+ capScope: "system",
32358
+ addonId: null,
32359
+ access: "create"
32360
+ },
32276
32361
  "lockControl.lock": {
32277
32362
  capName: "lock-control",
32278
32363
  capScope: "device",
@@ -35153,6 +35238,12 @@ Object.freeze({
35153
35238
  addonId: null,
35154
35239
  access: "create"
35155
35240
  },
35241
+ "terminalSession.updateInstance": {
35242
+ capName: "terminal-session",
35243
+ capScope: "system",
35244
+ addonId: null,
35245
+ access: "create"
35246
+ },
35156
35247
  "terminalSession.writeInput": {
35157
35248
  capName: "terminal-session",
35158
35249
  capScope: "system",
@@ -37640,6 +37731,35 @@ Object.freeze(Object.fromEntries([{
37640
37731
  }]
37641
37732
  }].map((s) => [s.stepId, s.defaultModelId])));
37642
37733
  string().min(1);
37734
+ var CLUSTER_STEP_SETTING_FIELDS = [{
37735
+ stepId: "face-embedding",
37736
+ key: "minLandmarkFaceSize",
37737
+ label: "Min face size for recognition (detection px)",
37738
+ description: "Refuse to embed a face smaller than this IN THE DETECTION FRAME. Applies to every node — the enrolled gallery is one index, and two admission floors would fill it from two policies. 0 disables the gate.",
37739
+ type: "slider",
37740
+ min: 0,
37741
+ max: 64,
37742
+ step: 2,
37743
+ default: 24
37744
+ }];
37745
+ function clusterStepSettingKey(stepId, fieldKey) {
37746
+ return `clusterStepSetting:${stepId}:${fieldKey}`;
37747
+ }
37748
+ var ClusterSettingNumberSchema = number().finite();
37749
+ function readClusterStepSettings(config) {
37750
+ const out = {};
37751
+ for (const field of CLUSTER_STEP_SETTING_FIELDS) {
37752
+ const parsed = ClusterSettingNumberSchema.safeParse(config[clusterStepSettingKey(field.stepId, field.key)]);
37753
+ const value = parsed.success ? parsed.data : field.default;
37754
+ const existing = out[field.stepId] ?? {};
37755
+ out[field.stepId] = {
37756
+ ...existing,
37757
+ [field.key]: value
37758
+ };
37759
+ }
37760
+ return out;
37761
+ }
37762
+ readClusterStepSettings({});
37643
37763
  object({
37644
37764
  /**
37645
37765
  * Fraction of the box's own size added on EACH side before cutting.
@@ -43347,7 +43467,8 @@ var TerminalCameraRelay = class {
43347
43467
  const opening = this.api.openSession(state.nodeId, {
43348
43468
  profileId: state.profileId,
43349
43469
  cols: 120,
43350
- rows: 40
43470
+ rows: 40,
43471
+ instanceId: state.instanceId
43351
43472
  });
43352
43473
  state.openPromise = opening;
43353
43474
  try {
@@ -43734,8 +43855,25 @@ var TerminalInstanceSchema = object({
43734
43855
  profileId: string().min(1).max(64),
43735
43856
  profileLabel: string().min(1).max(120),
43736
43857
  name: string().min(1).max(160),
43737
- enabled: boolean()
43858
+ enabled: boolean(),
43859
+ executable: string().max(1024).default(""),
43860
+ args: array(string().max(2048)).max(64).default([]),
43861
+ cwd: string().max(1024).default(""),
43862
+ environment: array(string().max(4096)).max(64).default([])
43738
43863
  });
43864
+ function environmentEntriesToRecord(entries) {
43865
+ const out = {};
43866
+ for (const entry of entries) {
43867
+ const eq = entry.indexOf("=");
43868
+ if (eq <= 0) continue;
43869
+ out[entry.slice(0, eq)] = entry.slice(eq + 1);
43870
+ }
43871
+ return out;
43872
+ }
43873
+ function environmentRecordToEntries(env) {
43874
+ if (env === void 0) return [];
43875
+ return Object.entries(env).map(([name, value]) => `${name}=${value}`);
43876
+ }
43739
43877
  /**
43740
43878
  * Config is operator-writable, so malformed or duplicate rows are ignored
43741
43879
  * rather than allowed to make declaration reconciliation destructive.
@@ -43999,7 +44137,11 @@ var TerminalSessionManager = class {
43999
44137
  return this.profiles.map((p) => ({
44000
44138
  profileId: p.profileId,
44001
44139
  label: p.label,
44002
- ...p.description !== void 0 ? { description: p.description } : {}
44140
+ ...p.description !== void 0 ? { description: p.description } : {},
44141
+ executable: p.file,
44142
+ args: [...p.args],
44143
+ cwd: p.cwd ?? "",
44144
+ environment: environmentRecordToEntries(p.env)
44003
44145
  }));
44004
44146
  }
44005
44147
  setInstanceControl(control) {
@@ -44011,6 +44153,9 @@ var TerminalSessionManager = class {
44011
44153
  async createInstance(input) {
44012
44154
  return this.requireInstanceControl().createInstance(input);
44013
44155
  }
44156
+ async updateInstance(input) {
44157
+ return this.requireInstanceControl().updateInstance(input);
44158
+ }
44014
44159
  async deleteInstance(input) {
44015
44160
  await this.requireInstanceControl().deleteInstance(input);
44016
44161
  }
@@ -44033,24 +44178,29 @@ var TerminalSessionManager = class {
44033
44178
  const cols = clampDimension(input.cols, MAX_COLS);
44034
44179
  const rows = clampDimension(input.rows, MAX_ROWS);
44035
44180
  const sessionId = this.mintId();
44181
+ const file = input.executable?.trim() || profile.file;
44182
+ const args = input.args !== void 0 ? [...input.args] : [...profile.args];
44183
+ const cwd = input.cwd?.trim() || profile.cwd || this.opts.cwd;
44184
+ const env = {
44185
+ ...this.opts.env,
44186
+ ...profile.env,
44187
+ ...environmentEntriesToRecord(input.environment ?? [])
44188
+ };
44036
44189
  const executable = [{
44037
- file: profile.file,
44038
- args: profile.args
44039
- }, ...profile.fallbacks ?? []].map((candidate) => ({
44190
+ file,
44191
+ args
44192
+ }, ...input.executable?.trim() ? [] : profile.fallbacks ?? []].map((candidate) => ({
44040
44193
  ...candidate,
44041
44194
  resolvedFile: this.resolveBinary(candidate.file)
44042
44195
  })).find((candidate) => candidate.resolvedFile !== null);
44043
- if (executable === void 0) throw new Error(`Terminal profile '${profile.profileId}' cannot start: executable '${profile.file}' was not found on PATH. Install it in the server image or set its path in Terminal settings.`);
44196
+ if (executable === void 0) throw new Error(`Terminal profile '${profile.profileId}' cannot start: executable '${file}' was not found on PATH. Install it in the server image or set its path in Terminal settings.`);
44044
44197
  const pty = this.opts.spawn({
44045
44198
  file: executable.resolvedFile,
44046
44199
  args: executable.args,
44047
44200
  cols,
44048
44201
  rows,
44049
- ...profile.cwd !== void 0 || this.opts.cwd !== void 0 ? { cwd: profile.cwd ?? this.opts.cwd } : {},
44050
- ...profile.env !== void 0 || this.opts.env !== void 0 ? { env: {
44051
- ...this.opts.env,
44052
- ...profile.env
44053
- } } : {}
44202
+ ...cwd !== void 0 && cwd !== "" ? { cwd } : {},
44203
+ ...Object.keys(env).length > 0 ? { env } : {}
44054
44204
  });
44055
44205
  const screen = this.opts.screenFactory(cols, rows);
44056
44206
  const info = {
@@ -44148,7 +44298,7 @@ var TerminalSessionManager = class {
44148
44298
  const oldestSeq = session.output[0]?.seq ?? session.nextSeq;
44149
44299
  if (input.afterSeq === 0 || input.afterSeq < oldestSeq - 1 || input.afterSeq > cursor) {
44150
44300
  await session.screen.flush();
44151
- const exit = [...session.output].reverse().find((event) => event.kind === "exit");
44301
+ const exit = session.output.toReversed().find((event) => event.kind === "exit");
44152
44302
  return {
44153
44303
  cursor,
44154
44304
  reset: true,
@@ -44392,7 +44542,21 @@ var TerminalAddon = class extends BaseAddon {
44392
44542
  const localNodeId = declarationOwnerNodeId(this.ctx.kernel.localNodeId);
44393
44543
  const cameraRelay = new TerminalCameraRelay({
44394
44544
  listProfiles: (nodeId) => nodeId === localNodeId ? manager.listProfiles() : this.ctx.api.terminalSession.listProfiles.query(void 0, nodePin(nodeId)),
44395
- openSession: (nodeId, input) => nodeId === localNodeId ? manager.openSession(input) : this.ctx.api.terminalSession.openSession.mutate(input, nodePin(nodeId)),
44545
+ openSession: (nodeId, input) => {
44546
+ const instance = input.instanceId ? this.terminalInstances().find((row) => row.id === input.instanceId) : void 0;
44547
+ const payload = {
44548
+ profileId: input.profileId,
44549
+ cols: input.cols,
44550
+ rows: input.rows,
44551
+ ...instance ? {
44552
+ ...instance.executable ? { executable: instance.executable } : {},
44553
+ ...instance.args.length > 0 ? { args: [...instance.args] } : {},
44554
+ ...instance.cwd ? { cwd: instance.cwd } : {},
44555
+ ...instance.environment.length > 0 ? { environment: [...instance.environment] } : {}
44556
+ } : {}
44557
+ };
44558
+ return nodeId === localNodeId ? manager.openSession(payload) : this.ctx.api.terminalSession.openSession.mutate(payload, nodePin(nodeId));
44559
+ },
44396
44560
  pullOutput: (nodeId, input) => nodeId === localNodeId ? manager.pullOutput(input) : this.ctx.api.terminalSession.pullOutput.mutate(input, nodePin(nodeId)),
44397
44561
  closeSession: async (nodeId, sessionId) => {
44398
44562
  if (nodeId === localNodeId) await manager.close({ sessionId });
@@ -44597,6 +44761,7 @@ var TerminalAddon = class extends BaseAddon {
44597
44761
  return {
44598
44762
  listInstances: async () => this.terminalInstances().map((instance) => this.instanceInfo(instance)),
44599
44763
  createInstance: async (input) => this.createTerminalInstance(input),
44764
+ updateInstance: async (input) => this.updateTerminalInstance(input),
44600
44765
  deleteInstance: async ({ instanceId }) => this.deleteTerminalInstance(instanceId),
44601
44766
  setInstanceEnabled: async ({ instanceId, enabled }) => this.setTerminalInstanceEnabled(instanceId, enabled),
44602
44767
  listLegacyCameras: async () => this.listLegacyTerminalCameras(),
@@ -44616,7 +44781,11 @@ var TerminalAddon = class extends BaseAddon {
44616
44781
  profileId: instance.profileId,
44617
44782
  profileLabel: instance.profileLabel,
44618
44783
  name: instance.name,
44619
- enabled: instance.enabled
44784
+ enabled: instance.enabled,
44785
+ executable: instance.executable,
44786
+ args: instance.args,
44787
+ cwd: instance.cwd,
44788
+ environment: instance.environment
44620
44789
  };
44621
44790
  }
44622
44791
  replaceTerminalCameraTombstones(stableIds) {
@@ -44644,11 +44813,35 @@ var TerminalAddon = class extends BaseAddon {
44644
44813
  profileId: profile.profileId,
44645
44814
  profileLabel: profile.label,
44646
44815
  name,
44647
- enabled: true
44816
+ enabled: true,
44817
+ executable: input.executable?.trim() || profile.executable || "",
44818
+ args: input.args ? [...input.args] : [...profile.args ?? []],
44819
+ cwd: input.cwd?.trim() || profile.cwd || "",
44820
+ environment: input.environment ? [...input.environment] : [...profile.environment ?? []]
44648
44821
  };
44649
44822
  await this.updateGlobalSettings({ terminalInstances: [...this.config.terminalInstances, instance] });
44650
44823
  return this.instanceInfo(instance);
44651
44824
  }
44825
+ async updateTerminalInstance(input) {
44826
+ const updated = await this.instanceMutationQueue.run(() => this.updateTerminalInstanceUnlocked(input));
44827
+ await this.cameraRelay?.closeInstance(updated.instanceId);
44828
+ await this.reconcileTerminalCameras();
44829
+ return updated;
44830
+ }
44831
+ async updateTerminalInstanceUnlocked(input) {
44832
+ const instance = this.terminalInstances().find((candidate) => candidate.id === input.instanceId);
44833
+ if (!instance) throw new Error(`No such Terminal instance: ${input.instanceId}`);
44834
+ const updated = {
44835
+ ...instance,
44836
+ ...input.name !== void 0 ? { name: input.name.trim() } : {},
44837
+ ...input.executable !== void 0 ? { executable: input.executable.trim() } : {},
44838
+ ...input.args !== void 0 ? { args: [...input.args] } : {},
44839
+ ...input.cwd !== void 0 ? { cwd: input.cwd.trim() } : {},
44840
+ ...input.environment !== void 0 ? { environment: [...input.environment] } : {}
44841
+ };
44842
+ await this.updateGlobalSettings({ terminalInstances: this.config.terminalInstances.map((candidate) => candidate.id === input.instanceId ? updated : candidate) });
44843
+ return this.instanceInfo(updated);
44844
+ }
44652
44845
  async deleteTerminalInstance(instanceId) {
44653
44846
  await this.instanceMutationQueue.run(() => this.deleteTerminalInstanceUnlocked(instanceId));
44654
44847
  await this.reconcileTerminalCameras();
@@ -44708,7 +44901,11 @@ var TerminalAddon = class extends BaseAddon {
44708
44901
  profileId: "monitor",
44709
44902
  profileLabel: legacy.profileLabel,
44710
44903
  name: requestedName?.trim() || legacy.name,
44711
- enabled: true
44904
+ enabled: true,
44905
+ executable: "",
44906
+ args: [],
44907
+ cwd: "",
44908
+ environment: []
44712
44909
  };
44713
44910
  await this.updateGlobalSettings({ terminalInstances: [...this.config.terminalInstances, instance] });
44714
44911
  return this.instanceInfo(instance);
package/dist/addon.mjs CHANGED
@@ -20009,7 +20009,12 @@ var TerminalSessionInfoSchema = object({
20009
20009
  var TerminalProfileInfoSchema = object({
20010
20010
  profileId: string(),
20011
20011
  label: string(),
20012
- description: string().optional()
20012
+ description: string().optional(),
20013
+ /** Spawn defaults the instance form copies on create. */
20014
+ executable: string().optional(),
20015
+ args: array(string()).readonly().optional(),
20016
+ cwd: string().optional(),
20017
+ environment: array(string()).readonly().optional()
20013
20018
  });
20014
20019
  /**
20015
20020
  * A durable operator-created Terminal instance. Profiles are templates; only
@@ -20022,7 +20027,11 @@ var TerminalInstanceInfoSchema = object({
20022
20027
  profileId: string(),
20023
20028
  profileLabel: string(),
20024
20029
  name: string(),
20025
- enabled: boolean()
20030
+ enabled: boolean(),
20031
+ executable: string(),
20032
+ args: array(string()).readonly(),
20033
+ cwd: string(),
20034
+ environment: array(string()).readonly()
20026
20035
  });
20027
20036
  var TerminalLegacyCameraSchema = object({
20028
20037
  stableId: string(),
@@ -20068,7 +20077,22 @@ var terminalSessionCapability = {
20068
20077
  createInstance: method(object({
20069
20078
  targetNodeId: string().min(1),
20070
20079
  profileId: string().min(1),
20071
- name: string().trim().min(1).max(160).optional()
20080
+ name: string().trim().min(1).max(160).optional(),
20081
+ executable: string().max(1024).optional(),
20082
+ args: array(string().max(2048)).max(64).optional(),
20083
+ cwd: string().max(1024).optional(),
20084
+ environment: array(string().max(4096)).max(64).optional()
20085
+ }), TerminalInstanceInfoSchema, {
20086
+ kind: "mutation",
20087
+ auth: "admin"
20088
+ }),
20089
+ updateInstance: method(object({
20090
+ instanceId: string().min(1),
20091
+ name: string().trim().min(1).max(160).optional(),
20092
+ executable: string().max(1024).optional(),
20093
+ args: array(string().max(2048)).max(64).optional(),
20094
+ cwd: string().max(1024).optional(),
20095
+ environment: array(string().max(4096)).max(64).optional()
20072
20096
  }), TerminalInstanceInfoSchema, {
20073
20097
  kind: "mutation",
20074
20098
  auth: "admin"
@@ -20103,7 +20127,11 @@ var terminalSessionCapability = {
20103
20127
  openSession: method(object({
20104
20128
  profileId: string(),
20105
20129
  cols: number().int().positive(),
20106
- rows: number().int().positive()
20130
+ rows: number().int().positive(),
20131
+ executable: string().max(1024).optional(),
20132
+ args: array(string().max(2048)).max(64).optional(),
20133
+ cwd: string().max(1024).optional(),
20134
+ environment: array(string().max(4096)).max(64).optional()
20107
20135
  }), TerminalSessionInfoSchema, {
20108
20136
  kind: "mutation",
20109
20137
  auth: "admin"
@@ -24158,6 +24186,20 @@ var AllowedAddressesSchema = object({
24158
24186
  * Network Addresses admin page and persisted by the addon.
24159
24187
  */
24160
24188
  addresses: array(string()).readonly() });
24189
+ var TlsStatusSchema = object({
24190
+ mode: _enum([
24191
+ "generated",
24192
+ "uploaded",
24193
+ "disabled"
24194
+ ]),
24195
+ leafFingerprintSha256: string().nullable(),
24196
+ caFingerprintSha256: string().nullable(),
24197
+ validTo: string().nullable(),
24198
+ sans: array(string()),
24199
+ caCertPem: string().nullable(),
24200
+ reissueError: string().nullable(),
24201
+ restartRequired: boolean()
24202
+ });
24161
24203
  method(_void(), ListResultSchema), method(_void(), PreferredSchema), method(object({
24162
24204
  /**
24163
24205
  * LEGACY HINT — do not send from new code. Kept optional so clients
@@ -24177,7 +24219,20 @@ method(_void(), ListResultSchema), method(_void(), PreferredSchema), method(obje
24177
24219
  * to avoid mixed-content blocks in the browser. The public
24178
24220
  * tunnel always emits `https://` regardless. */
24179
24221
  scheme: _enum(["http", "https"]).optional()
24180
- }), GetConnectionEndpointsResultSchema), method(_void(), NotificationEndpointSchema), method(object({ baseUrl: string().nullable() }), NotificationEndpointSchema, { kind: "mutation" }), method(_void(), AllowedAddressesSchema), method(AllowedAddressesSchema, object({ success: literal(true) }), { kind: "mutation" }), method(_void(), AllowedAddressesSchema, { kind: "mutation" });
24222
+ }), GetConnectionEndpointsResultSchema), method(_void(), NotificationEndpointSchema), method(object({ baseUrl: string().nullable() }), NotificationEndpointSchema, { kind: "mutation" }), method(_void(), AllowedAddressesSchema), method(AllowedAddressesSchema, object({ success: literal(true) }), { kind: "mutation" }), method(_void(), AllowedAddressesSchema, { kind: "mutation" }), method(_void(), TlsStatusSchema), method(object({ reason: string().optional() }), TlsStatusSchema, {
24223
+ kind: "mutation",
24224
+ auth: "admin"
24225
+ }), method(object({
24226
+ certPem: string().min(1),
24227
+ keyPem: string().min(1),
24228
+ caPem: string().optional()
24229
+ }), TlsStatusSchema, {
24230
+ kind: "mutation",
24231
+ auth: "admin"
24232
+ }), method(_void(), object({ pem: string() })), method(_void(), TlsStatusSchema, {
24233
+ kind: "mutation",
24234
+ auth: "admin"
24235
+ });
24181
24236
  var LockControlStatusSchema = object({
24182
24237
  /** Lifecycle state of the lock. `jammed` means the motor reported
24183
24238
  * failure to reach the target — operator intervention required. */
@@ -32202,6 +32257,12 @@ Object.freeze({
32202
32257
  addonId: null,
32203
32258
  access: "create"
32204
32259
  },
32260
+ "localNetwork.downloadCa": {
32261
+ capName: "local-network",
32262
+ capScope: "system",
32263
+ addonId: null,
32264
+ access: "view"
32265
+ },
32205
32266
  "localNetwork.getAllowedAddresses": {
32206
32267
  capName: "local-network",
32207
32268
  capScope: "system",
@@ -32226,18 +32287,36 @@ Object.freeze({
32226
32287
  addonId: null,
32227
32288
  access: "view"
32228
32289
  },
32290
+ "localNetwork.getTlsStatus": {
32291
+ capName: "local-network",
32292
+ capScope: "system",
32293
+ addonId: null,
32294
+ access: "view"
32295
+ },
32229
32296
  "localNetwork.list": {
32230
32297
  capName: "local-network",
32231
32298
  capScope: "system",
32232
32299
  addonId: null,
32233
32300
  access: "view"
32234
32301
  },
32302
+ "localNetwork.regenerateCertificate": {
32303
+ capName: "local-network",
32304
+ capScope: "system",
32305
+ addonId: null,
32306
+ access: "create"
32307
+ },
32235
32308
  "localNetwork.resetAllowlistToBestMatch": {
32236
32309
  capName: "local-network",
32237
32310
  capScope: "system",
32238
32311
  addonId: null,
32239
32312
  access: "delete"
32240
32313
  },
32314
+ "localNetwork.revertToGeneratedCertificate": {
32315
+ capName: "local-network",
32316
+ capScope: "system",
32317
+ addonId: null,
32318
+ access: "create"
32319
+ },
32241
32320
  "localNetwork.setAllowedAddresses": {
32242
32321
  capName: "local-network",
32243
32322
  capScope: "system",
@@ -32250,6 +32329,12 @@ Object.freeze({
32250
32329
  addonId: null,
32251
32330
  access: "create"
32252
32331
  },
32332
+ "localNetwork.uploadCertificate": {
32333
+ capName: "local-network",
32334
+ capScope: "system",
32335
+ addonId: null,
32336
+ access: "create"
32337
+ },
32253
32338
  "lockControl.lock": {
32254
32339
  capName: "lock-control",
32255
32340
  capScope: "device",
@@ -35130,6 +35215,12 @@ Object.freeze({
35130
35215
  addonId: null,
35131
35216
  access: "create"
35132
35217
  },
35218
+ "terminalSession.updateInstance": {
35219
+ capName: "terminal-session",
35220
+ capScope: "system",
35221
+ addonId: null,
35222
+ access: "create"
35223
+ },
35133
35224
  "terminalSession.writeInput": {
35134
35225
  capName: "terminal-session",
35135
35226
  capScope: "system",
@@ -37617,6 +37708,35 @@ Object.freeze(Object.fromEntries([{
37617
37708
  }]
37618
37709
  }].map((s) => [s.stepId, s.defaultModelId])));
37619
37710
  string().min(1);
37711
+ var CLUSTER_STEP_SETTING_FIELDS = [{
37712
+ stepId: "face-embedding",
37713
+ key: "minLandmarkFaceSize",
37714
+ label: "Min face size for recognition (detection px)",
37715
+ description: "Refuse to embed a face smaller than this IN THE DETECTION FRAME. Applies to every node — the enrolled gallery is one index, and two admission floors would fill it from two policies. 0 disables the gate.",
37716
+ type: "slider",
37717
+ min: 0,
37718
+ max: 64,
37719
+ step: 2,
37720
+ default: 24
37721
+ }];
37722
+ function clusterStepSettingKey(stepId, fieldKey) {
37723
+ return `clusterStepSetting:${stepId}:${fieldKey}`;
37724
+ }
37725
+ var ClusterSettingNumberSchema = number().finite();
37726
+ function readClusterStepSettings(config) {
37727
+ const out = {};
37728
+ for (const field of CLUSTER_STEP_SETTING_FIELDS) {
37729
+ const parsed = ClusterSettingNumberSchema.safeParse(config[clusterStepSettingKey(field.stepId, field.key)]);
37730
+ const value = parsed.success ? parsed.data : field.default;
37731
+ const existing = out[field.stepId] ?? {};
37732
+ out[field.stepId] = {
37733
+ ...existing,
37734
+ [field.key]: value
37735
+ };
37736
+ }
37737
+ return out;
37738
+ }
37739
+ readClusterStepSettings({});
37620
37740
  object({
37621
37741
  /**
37622
37742
  * Fraction of the box's own size added on EACH side before cutting.
@@ -43324,7 +43444,8 @@ var TerminalCameraRelay = class {
43324
43444
  const opening = this.api.openSession(state.nodeId, {
43325
43445
  profileId: state.profileId,
43326
43446
  cols: 120,
43327
- rows: 40
43447
+ rows: 40,
43448
+ instanceId: state.instanceId
43328
43449
  });
43329
43450
  state.openPromise = opening;
43330
43451
  try {
@@ -43711,8 +43832,25 @@ var TerminalInstanceSchema = object({
43711
43832
  profileId: string().min(1).max(64),
43712
43833
  profileLabel: string().min(1).max(120),
43713
43834
  name: string().min(1).max(160),
43714
- enabled: boolean()
43835
+ enabled: boolean(),
43836
+ executable: string().max(1024).default(""),
43837
+ args: array(string().max(2048)).max(64).default([]),
43838
+ cwd: string().max(1024).default(""),
43839
+ environment: array(string().max(4096)).max(64).default([])
43715
43840
  });
43841
+ function environmentEntriesToRecord(entries) {
43842
+ const out = {};
43843
+ for (const entry of entries) {
43844
+ const eq = entry.indexOf("=");
43845
+ if (eq <= 0) continue;
43846
+ out[entry.slice(0, eq)] = entry.slice(eq + 1);
43847
+ }
43848
+ return out;
43849
+ }
43850
+ function environmentRecordToEntries(env) {
43851
+ if (env === void 0) return [];
43852
+ return Object.entries(env).map(([name, value]) => `${name}=${value}`);
43853
+ }
43716
43854
  /**
43717
43855
  * Config is operator-writable, so malformed or duplicate rows are ignored
43718
43856
  * rather than allowed to make declaration reconciliation destructive.
@@ -43976,7 +44114,11 @@ var TerminalSessionManager = class {
43976
44114
  return this.profiles.map((p) => ({
43977
44115
  profileId: p.profileId,
43978
44116
  label: p.label,
43979
- ...p.description !== void 0 ? { description: p.description } : {}
44117
+ ...p.description !== void 0 ? { description: p.description } : {},
44118
+ executable: p.file,
44119
+ args: [...p.args],
44120
+ cwd: p.cwd ?? "",
44121
+ environment: environmentRecordToEntries(p.env)
43980
44122
  }));
43981
44123
  }
43982
44124
  setInstanceControl(control) {
@@ -43988,6 +44130,9 @@ var TerminalSessionManager = class {
43988
44130
  async createInstance(input) {
43989
44131
  return this.requireInstanceControl().createInstance(input);
43990
44132
  }
44133
+ async updateInstance(input) {
44134
+ return this.requireInstanceControl().updateInstance(input);
44135
+ }
43991
44136
  async deleteInstance(input) {
43992
44137
  await this.requireInstanceControl().deleteInstance(input);
43993
44138
  }
@@ -44010,24 +44155,29 @@ var TerminalSessionManager = class {
44010
44155
  const cols = clampDimension(input.cols, MAX_COLS);
44011
44156
  const rows = clampDimension(input.rows, MAX_ROWS);
44012
44157
  const sessionId = this.mintId();
44158
+ const file = input.executable?.trim() || profile.file;
44159
+ const args = input.args !== void 0 ? [...input.args] : [...profile.args];
44160
+ const cwd = input.cwd?.trim() || profile.cwd || this.opts.cwd;
44161
+ const env = {
44162
+ ...this.opts.env,
44163
+ ...profile.env,
44164
+ ...environmentEntriesToRecord(input.environment ?? [])
44165
+ };
44013
44166
  const executable = [{
44014
- file: profile.file,
44015
- args: profile.args
44016
- }, ...profile.fallbacks ?? []].map((candidate) => ({
44167
+ file,
44168
+ args
44169
+ }, ...input.executable?.trim() ? [] : profile.fallbacks ?? []].map((candidate) => ({
44017
44170
  ...candidate,
44018
44171
  resolvedFile: this.resolveBinary(candidate.file)
44019
44172
  })).find((candidate) => candidate.resolvedFile !== null);
44020
- if (executable === void 0) throw new Error(`Terminal profile '${profile.profileId}' cannot start: executable '${profile.file}' was not found on PATH. Install it in the server image or set its path in Terminal settings.`);
44173
+ if (executable === void 0) throw new Error(`Terminal profile '${profile.profileId}' cannot start: executable '${file}' was not found on PATH. Install it in the server image or set its path in Terminal settings.`);
44021
44174
  const pty = this.opts.spawn({
44022
44175
  file: executable.resolvedFile,
44023
44176
  args: executable.args,
44024
44177
  cols,
44025
44178
  rows,
44026
- ...profile.cwd !== void 0 || this.opts.cwd !== void 0 ? { cwd: profile.cwd ?? this.opts.cwd } : {},
44027
- ...profile.env !== void 0 || this.opts.env !== void 0 ? { env: {
44028
- ...this.opts.env,
44029
- ...profile.env
44030
- } } : {}
44179
+ ...cwd !== void 0 && cwd !== "" ? { cwd } : {},
44180
+ ...Object.keys(env).length > 0 ? { env } : {}
44031
44181
  });
44032
44182
  const screen = this.opts.screenFactory(cols, rows);
44033
44183
  const info = {
@@ -44125,7 +44275,7 @@ var TerminalSessionManager = class {
44125
44275
  const oldestSeq = session.output[0]?.seq ?? session.nextSeq;
44126
44276
  if (input.afterSeq === 0 || input.afterSeq < oldestSeq - 1 || input.afterSeq > cursor) {
44127
44277
  await session.screen.flush();
44128
- const exit = [...session.output].reverse().find((event) => event.kind === "exit");
44278
+ const exit = session.output.toReversed().find((event) => event.kind === "exit");
44129
44279
  return {
44130
44280
  cursor,
44131
44281
  reset: true,
@@ -44369,7 +44519,21 @@ var TerminalAddon = class extends BaseAddon {
44369
44519
  const localNodeId = declarationOwnerNodeId(this.ctx.kernel.localNodeId);
44370
44520
  const cameraRelay = new TerminalCameraRelay({
44371
44521
  listProfiles: (nodeId) => nodeId === localNodeId ? manager.listProfiles() : this.ctx.api.terminalSession.listProfiles.query(void 0, nodePin(nodeId)),
44372
- openSession: (nodeId, input) => nodeId === localNodeId ? manager.openSession(input) : this.ctx.api.terminalSession.openSession.mutate(input, nodePin(nodeId)),
44522
+ openSession: (nodeId, input) => {
44523
+ const instance = input.instanceId ? this.terminalInstances().find((row) => row.id === input.instanceId) : void 0;
44524
+ const payload = {
44525
+ profileId: input.profileId,
44526
+ cols: input.cols,
44527
+ rows: input.rows,
44528
+ ...instance ? {
44529
+ ...instance.executable ? { executable: instance.executable } : {},
44530
+ ...instance.args.length > 0 ? { args: [...instance.args] } : {},
44531
+ ...instance.cwd ? { cwd: instance.cwd } : {},
44532
+ ...instance.environment.length > 0 ? { environment: [...instance.environment] } : {}
44533
+ } : {}
44534
+ };
44535
+ return nodeId === localNodeId ? manager.openSession(payload) : this.ctx.api.terminalSession.openSession.mutate(payload, nodePin(nodeId));
44536
+ },
44373
44537
  pullOutput: (nodeId, input) => nodeId === localNodeId ? manager.pullOutput(input) : this.ctx.api.terminalSession.pullOutput.mutate(input, nodePin(nodeId)),
44374
44538
  closeSession: async (nodeId, sessionId) => {
44375
44539
  if (nodeId === localNodeId) await manager.close({ sessionId });
@@ -44574,6 +44738,7 @@ var TerminalAddon = class extends BaseAddon {
44574
44738
  return {
44575
44739
  listInstances: async () => this.terminalInstances().map((instance) => this.instanceInfo(instance)),
44576
44740
  createInstance: async (input) => this.createTerminalInstance(input),
44741
+ updateInstance: async (input) => this.updateTerminalInstance(input),
44577
44742
  deleteInstance: async ({ instanceId }) => this.deleteTerminalInstance(instanceId),
44578
44743
  setInstanceEnabled: async ({ instanceId, enabled }) => this.setTerminalInstanceEnabled(instanceId, enabled),
44579
44744
  listLegacyCameras: async () => this.listLegacyTerminalCameras(),
@@ -44593,7 +44758,11 @@ var TerminalAddon = class extends BaseAddon {
44593
44758
  profileId: instance.profileId,
44594
44759
  profileLabel: instance.profileLabel,
44595
44760
  name: instance.name,
44596
- enabled: instance.enabled
44761
+ enabled: instance.enabled,
44762
+ executable: instance.executable,
44763
+ args: instance.args,
44764
+ cwd: instance.cwd,
44765
+ environment: instance.environment
44597
44766
  };
44598
44767
  }
44599
44768
  replaceTerminalCameraTombstones(stableIds) {
@@ -44621,11 +44790,35 @@ var TerminalAddon = class extends BaseAddon {
44621
44790
  profileId: profile.profileId,
44622
44791
  profileLabel: profile.label,
44623
44792
  name,
44624
- enabled: true
44793
+ enabled: true,
44794
+ executable: input.executable?.trim() || profile.executable || "",
44795
+ args: input.args ? [...input.args] : [...profile.args ?? []],
44796
+ cwd: input.cwd?.trim() || profile.cwd || "",
44797
+ environment: input.environment ? [...input.environment] : [...profile.environment ?? []]
44625
44798
  };
44626
44799
  await this.updateGlobalSettings({ terminalInstances: [...this.config.terminalInstances, instance] });
44627
44800
  return this.instanceInfo(instance);
44628
44801
  }
44802
+ async updateTerminalInstance(input) {
44803
+ const updated = await this.instanceMutationQueue.run(() => this.updateTerminalInstanceUnlocked(input));
44804
+ await this.cameraRelay?.closeInstance(updated.instanceId);
44805
+ await this.reconcileTerminalCameras();
44806
+ return updated;
44807
+ }
44808
+ async updateTerminalInstanceUnlocked(input) {
44809
+ const instance = this.terminalInstances().find((candidate) => candidate.id === input.instanceId);
44810
+ if (!instance) throw new Error(`No such Terminal instance: ${input.instanceId}`);
44811
+ const updated = {
44812
+ ...instance,
44813
+ ...input.name !== void 0 ? { name: input.name.trim() } : {},
44814
+ ...input.executable !== void 0 ? { executable: input.executable.trim() } : {},
44815
+ ...input.args !== void 0 ? { args: [...input.args] } : {},
44816
+ ...input.cwd !== void 0 ? { cwd: input.cwd.trim() } : {},
44817
+ ...input.environment !== void 0 ? { environment: [...input.environment] } : {}
44818
+ };
44819
+ await this.updateGlobalSettings({ terminalInstances: this.config.terminalInstances.map((candidate) => candidate.id === input.instanceId ? updated : candidate) });
44820
+ return this.instanceInfo(updated);
44821
+ }
44629
44822
  async deleteTerminalInstance(instanceId) {
44630
44823
  await this.instanceMutationQueue.run(() => this.deleteTerminalInstanceUnlocked(instanceId));
44631
44824
  await this.reconcileTerminalCameras();
@@ -44685,7 +44878,11 @@ var TerminalAddon = class extends BaseAddon {
44685
44878
  profileId: "monitor",
44686
44879
  profileLabel: legacy.profileLabel,
44687
44880
  name: requestedName?.trim() || legacy.name,
44688
- enabled: true
44881
+ enabled: true,
44882
+ executable: "",
44883
+ args: [],
44884
+ cwd: "",
44885
+ environment: []
44689
44886
  };
44690
44887
  await this.updateGlobalSettings({ terminalInstances: [...this.config.terminalInstances, instance] });
44691
44888
  return this.instanceInfo(instance);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-terminal",
3
- "version": "0.1.29",
3
+ "version": "0.1.30",
4
4
  "description": "Interactive terminal sessions (pty + xterm) as a CamStack addon",
5
5
  "keywords": [
6
6
  "camstack",