@skrr-ai/cli 0.1.49 → 0.1.51

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 (180) hide show
  1. package/README.md +5 -4
  2. package/dist/base-command.js +75 -6
  3. package/dist/commands/agents/chat.d.ts +14 -0
  4. package/dist/commands/agents/chat.js +37 -1
  5. package/dist/commands/agents/create.js +14 -7
  6. package/dist/commands/agents/show.d.ts +9 -0
  7. package/dist/commands/agents/show.js +53 -0
  8. package/dist/commands/agents/update.d.ts +9 -0
  9. package/dist/commands/agents/update.js +38 -5
  10. package/dist/commands/balance/show.d.ts +34 -0
  11. package/dist/commands/balance/show.js +92 -2
  12. package/dist/commands/code/index.d.ts +1 -0
  13. package/dist/commands/code/index.js +49 -5
  14. package/dist/commands/code/run.js +5 -1
  15. package/dist/commands/commitments/cycles.d.ts +19 -0
  16. package/dist/commands/commitments/cycles.js +46 -0
  17. package/dist/commands/commitments/doctor.d.ts +1 -0
  18. package/dist/commands/commitments/doctor.js +19 -4
  19. package/dist/commands/commitments/effective-policy.js +40 -1
  20. package/dist/commands/commitments/explain.d.ts +17 -0
  21. package/dist/commands/commitments/explain.js +40 -0
  22. package/dist/commands/commitments/next-tick.js +3 -1
  23. package/dist/commands/commitments/preflight.js +10 -0
  24. package/dist/commands/commitments/show.js +11 -1
  25. package/dist/commands/daemon/config.d.ts +15 -0
  26. package/dist/commands/daemon/config.js +38 -0
  27. package/dist/commands/daemon/index.js +1 -0
  28. package/dist/commands/daemon/status.js +15 -1
  29. package/dist/commands/goals/create.d.ts +1 -0
  30. package/dist/commands/goals/create.js +22 -0
  31. package/dist/commands/harnesses/models.d.ts +54 -0
  32. package/dist/commands/harnesses/models.js +100 -7
  33. package/dist/commands/harnesses/show.d.ts +11 -0
  34. package/dist/commands/harnesses/show.js +35 -0
  35. package/dist/commands/harnesses/update.d.ts +28 -1
  36. package/dist/commands/harnesses/update.js +73 -10
  37. package/dist/commands/logout.js +12 -1
  38. package/dist/commands/machines/dedicated/index.js +1 -1
  39. package/dist/commands/machines/dedicated/sign-in.js +4 -1
  40. package/dist/commands/machines/dedicated/terminal/kill.d.ts +21 -0
  41. package/dist/commands/machines/dedicated/terminal/kill.js +55 -0
  42. package/dist/commands/machines/dedicated/terminal/ls.d.ts +22 -0
  43. package/dist/commands/machines/dedicated/terminal/ls.js +70 -0
  44. package/dist/commands/machines/dedicated/terminal/rename.d.ts +23 -0
  45. package/dist/commands/machines/dedicated/terminal/rename.js +64 -0
  46. package/dist/commands/machines/dedicated/terminal.d.ts +9 -0
  47. package/dist/commands/machines/dedicated/terminal.js +27 -2
  48. package/dist/commands/machines/hosted/connect.js +2 -2
  49. package/dist/commands/machines/hosted/destroy.js +1 -1
  50. package/dist/commands/machines/hosted/exec.js +1 -1
  51. package/dist/commands/machines/hosted/list.js +1 -1
  52. package/dist/commands/machines/hosted/pause.js +1 -1
  53. package/dist/commands/machines/hosted/pull.js +1 -1
  54. package/dist/commands/machines/hosted/resume.js +1 -1
  55. package/dist/commands/machines/hosted/start.js +2 -2
  56. package/dist/commands/machines/hosted/status.js +2 -1
  57. package/dist/commands/outbox/list.d.ts +12 -1
  58. package/dist/commands/outbox/list.js +31 -2
  59. package/dist/commands/revoke-daemon.js +9 -0
  60. package/dist/commands/spaces/create.d.ts +1 -0
  61. package/dist/commands/spaces/create.js +17 -0
  62. package/dist/commands/spaces/show.js +5 -0
  63. package/dist/commands/spaces/update.d.ts +1 -0
  64. package/dist/commands/spaces/update.js +16 -0
  65. package/dist/commands/tasks/create.d.ts +1 -0
  66. package/dist/commands/tasks/create.js +6 -0
  67. package/dist/commands/tasks/list.js +1 -0
  68. package/dist/commands/tasks/show.js +45 -0
  69. package/dist/commands/tasks/update.d.ts +1 -0
  70. package/dist/commands/tasks/update.js +22 -0
  71. package/dist/commands/tasks/upsert.d.ts +1 -0
  72. package/dist/commands/tasks/upsert.js +16 -0
  73. package/dist/lib/agent-config.d.ts +2 -0
  74. package/dist/lib/agent-config.js +3 -1
  75. package/dist/lib/agentic-stream.d.ts +68 -1
  76. package/dist/lib/agentic-stream.js +294 -7
  77. package/dist/lib/api-fetch.js +19 -0
  78. package/dist/lib/auth-core-init.d.ts +2 -10
  79. package/dist/lib/auth-core-init.js +20 -67
  80. package/dist/lib/auth-failure.d.ts +33 -0
  81. package/dist/lib/auth-failure.js +65 -0
  82. package/dist/lib/auth-storage.d.ts +14 -0
  83. package/dist/lib/auth-storage.js +14 -0
  84. package/dist/lib/commitments.d.ts +37 -2
  85. package/dist/lib/commitments.js +148 -0
  86. package/dist/lib/daemonBroker.d.ts +134 -31
  87. package/dist/lib/daemonBroker.js +376 -31
  88. package/dist/lib/dedicated-lease-command.d.ts +14 -0
  89. package/dist/lib/dedicated-lease-command.js +30 -1
  90. package/dist/lib/dedicated-machines.js +8 -25
  91. package/dist/lib/dedicated-service-command.d.ts +11 -3
  92. package/dist/lib/dedicated-service-command.js +20 -3
  93. package/dist/lib/dedicated-service.d.ts +46 -0
  94. package/dist/lib/dedicated-service.js +85 -7
  95. package/dist/lib/dedicated-terminal.d.ts +21 -0
  96. package/dist/lib/dedicated-terminal.js +104 -9
  97. package/dist/lib/delegated-cli.js +4 -1
  98. package/dist/lib/exec-runtime-binary.d.ts +3 -1
  99. package/dist/lib/exec-runtime-binary.js +4 -2
  100. package/dist/lib/first-party-harness-broker.d.ts +10 -0
  101. package/dist/lib/first-party-harness-broker.js +13 -1
  102. package/dist/lib/first-party-harness-doctor.js +41 -1
  103. package/dist/lib/first-party-harness-managed.d.ts +4 -3
  104. package/dist/lib/first-party-harness-project-trust.d.ts +125 -0
  105. package/dist/lib/first-party-harness-project-trust.js +364 -0
  106. package/dist/lib/first-party-harness.d.ts +9 -2
  107. package/dist/lib/first-party-harness.js +6 -5
  108. package/dist/lib/harnesses.d.ts +10 -0
  109. package/dist/lib/hosted-machines.d.ts +10 -1
  110. package/dist/lib/hosted-machines.js +36 -2
  111. package/dist/lib/live-runtime-binary.d.ts +40 -0
  112. package/dist/lib/live-runtime-binary.js +193 -0
  113. package/dist/lib/login.d.ts +23 -0
  114. package/dist/lib/login.js +60 -10
  115. package/dist/lib/machine-spend-cap.d.ts +23 -0
  116. package/dist/lib/machine-spend-cap.js +68 -0
  117. package/dist/lib/message-intent.js +3 -1
  118. package/dist/lib/node-adapter.js +15 -3
  119. package/dist/lib/tasks.d.ts +20 -0
  120. package/dist/lib/tasks.js +54 -0
  121. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/cliHandoffWire.d.ts +90 -0
  122. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/cliHandoffWire.js +113 -0
  123. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/credentialEnvelopeBridge.d.ts +2 -0
  124. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/credentialEnvelopeBridge.js +26 -1
  125. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/credentialSession.d.ts +47 -0
  126. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/credentialSession.js +69 -0
  127. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/daemonToolApproval.d.ts +133 -0
  128. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/daemonToolApproval.js +189 -0
  129. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceIdentityBridge.js +41 -3
  130. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.d.ts +3 -1
  131. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.js +25 -3
  132. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/loopbackHttp.d.ts +32 -0
  133. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/loopbackHttp.js +268 -0
  134. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/machineId.d.ts +2 -2
  135. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/machineId.js +11 -10
  136. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/machineUuid.d.ts +36 -0
  137. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/machineUuid.js +156 -0
  138. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/messages.d.ts +8 -3
  139. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/messages.js +9 -4
  140. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/refresh.d.ts +8 -0
  141. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/refresh.js +16 -10
  142. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/refreshClassification.d.ts +6 -0
  143. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/refreshClassification.js +32 -9
  144. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/sessionPermissionAuthority.d.ts +81 -0
  145. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/sessionPermissionAuthority.js +87 -0
  146. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/spawnEnv.d.ts +22 -0
  147. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/spawnEnv.js +61 -2
  148. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/types.d.ts +6 -1
  149. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/cliHandoffWire.d.ts +90 -0
  150. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/cliHandoffWire.js +105 -0
  151. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/credentialEnvelopeBridge.d.ts +2 -0
  152. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/credentialEnvelopeBridge.js +25 -1
  153. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/credentialSession.d.ts +47 -0
  154. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/credentialSession.js +66 -0
  155. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/daemonToolApproval.d.ts +133 -0
  156. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/daemonToolApproval.js +182 -0
  157. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceIdentityBridge.js +41 -3
  158. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.d.ts +3 -1
  159. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.js +10 -1
  160. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/loopbackHttp.d.ts +32 -0
  161. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/loopbackHttp.js +257 -0
  162. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/machineId.d.ts +2 -2
  163. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/machineId.js +11 -10
  164. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/machineUuid.d.ts +36 -0
  165. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/machineUuid.js +147 -0
  166. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/messages.d.ts +8 -3
  167. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/messages.js +9 -4
  168. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/refresh.d.ts +8 -0
  169. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/refresh.js +16 -10
  170. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/refreshClassification.d.ts +6 -0
  171. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/refreshClassification.js +31 -9
  172. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/sessionPermissionAuthority.d.ts +81 -0
  173. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/sessionPermissionAuthority.js +80 -0
  174. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/spawnEnv.d.ts +22 -0
  175. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/spawnEnv.js +58 -1
  176. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/types.d.ts +6 -1
  177. package/dist/node_modules/@skrr-ai/auth-core/package.json +61 -1
  178. package/dist/node_modules/@skrr-ai/data-provider/index.js +3418 -3344
  179. package/oclif.manifest.json +24813 -24242
  180. package/package.json +4 -1
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.readinessPointer = readinessPointer;
4
4
  const core_1 = require("@oclif/core");
5
5
  const exec_runtime_binary_1 = require("../../lib/exec-runtime-binary");
6
+ const live_runtime_binary_1 = require("../../lib/live-runtime-binary");
6
7
  /**
7
8
  * The status report is the runtime's, and it describes the daemon: running,
8
9
  * connected, which credential. Whether the coding-agent CLIs on the machine can
@@ -41,7 +42,20 @@ class DaemonStatus extends core_1.Command {
41
42
  // mistake, one directory over.
42
43
  const argv = this.argv;
43
44
  try {
44
- const code = await (0, exec_runtime_binary_1.execRuntimeBinary)(['status', ...argv]);
45
+ // Status describes the LIVE daemon, so render it with the live daemon's
46
+ // own binary when that is a different build from the first `skrrd` on
47
+ // PATH, and say which one rendered it either way (OSK-12102). The note
48
+ // goes to stderr so `--json` stays one document on stdout.
49
+ const pathBinary = (0, exec_runtime_binary_1.findRuntimeBinary)();
50
+ const decision = pathBinary
51
+ ? (0, live_runtime_binary_1.decideStatusBinary)(pathBinary, (0, live_runtime_binary_1.profileFromArgs)(argv), (0, live_runtime_binary_1.defaultLiveRuntimeDeps)())
52
+ : null;
53
+ const note = decision ? (0, live_runtime_binary_1.describeStatusBinary)(decision) : null;
54
+ if (note)
55
+ process.stderr.write(`${note}\n`);
56
+ const code = await (0, exec_runtime_binary_1.execRuntimeBinary)(['status', ...argv], {
57
+ binary: decision?.binary,
58
+ });
45
59
  const pointer = readinessPointer(argv, code);
46
60
  if (pointer)
47
61
  this.log(pointer);
@@ -10,6 +10,7 @@ export default class GoalsCreate extends BaseCommand {
10
10
  level: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
11
11
  'target-date': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
12
12
  space: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
13
+ label: import("@oclif/core/lib/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
13
14
  project: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
14
15
  parent: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
15
16
  'owner-agent': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
@@ -5,6 +5,7 @@ const data_provider_1 = require("@skrr-ai/data-provider");
5
5
  const base_command_1 = require("../../base-command");
6
6
  const web_url_1 = require("../../lib/web-url");
7
7
  const prompt_1 = require("../../lib/prompt");
8
+ const label_ref_1 = require("../../lib/label-ref");
8
9
  const projects_1 = require("../../lib/projects");
9
10
  const created_line_1 = require("../../lib/created-line");
10
11
  class GoalsCreate extends base_command_1.BaseCommand {
@@ -30,6 +31,10 @@ class GoalsCreate extends base_command_1.BaseCommand {
30
31
  }),
31
32
  'target-date': core_1.Flags.string({ description: 'Target date (YYYY-MM-DD or ISO timestamp)' }),
32
33
  space: core_1.Flags.string({ description: 'Space ID to link this goal into after creation' }),
34
+ label: core_1.Flags.string({
35
+ description: 'Attach a label (repeatable) — id, or a name from `skrr labels list --applicable-to goal`. An unknown name is refused, never created.',
36
+ multiple: true,
37
+ }),
33
38
  project: core_1.Flags.string({
34
39
  description: 'Project to contain this goal — id, or a ref (OSK-P-12) alongside --space, or a qualified ref (OSK-S-3/OSK-P-12)',
35
40
  }),
@@ -102,6 +107,23 @@ class GoalsCreate extends base_command_1.BaseCommand {
102
107
  exit: 1,
103
108
  });
104
109
  }
110
+ let resolvedLabelIds;
111
+ try {
112
+ const ids = await (0, label_ref_1.resolveLabelRefs)(flags.label, {
113
+ applicableTo: 'goal',
114
+ ...(process.env.OVERSKY_WORKSPACE_ID
115
+ ? { workspaceId: process.env.OVERSKY_WORKSPACE_ID }
116
+ : {}),
117
+ });
118
+ resolvedLabelIds = ids.length > 0 ? ids : undefined;
119
+ }
120
+ catch (err) {
121
+ if (err instanceof label_ref_1.LabelRefError)
122
+ this.error(err.message, { exit: 1 });
123
+ this.handleApiError(err);
124
+ }
125
+ if (resolvedLabelIds)
126
+ payload.labelIds = resolvedLabelIds;
105
127
  let created;
106
128
  try {
107
129
  created = await data_provider_1.dataService.createGoal(payload);
@@ -1,4 +1,58 @@
1
1
  import { BaseCommand } from '../../base-command';
2
+ /**
3
+ * Why the server fell back to its curated list — `sourceReason` on
4
+ * `GET /api/harnesses/:id/models`, present only when `source` is `curated`.
5
+ * The server owns the vocabulary (`MODEL_SOURCE_REASONS` in
6
+ * `api/server/services/Harness/engineModelCatalog.js`); a value this build
7
+ * does not recognise still prints, verbatim, rather than vanishing.
8
+ */
9
+ export type HarnessModelSourceReason = 'no_live_catalog_for_harness' | 'daemon_offline' | 'live_catalog_unsupported' | 'live_probe_failed';
10
+ export interface HarnessModelsReport {
11
+ provider?: string | null;
12
+ source?: string;
13
+ sourceReason?: HarnessModelSourceReason | string;
14
+ sourceDetail?: string;
15
+ /** The harness row's own stored default model; empty when unset. */
16
+ defaultModel?: string | null;
17
+ /** The harness row's own stored default effort; empty when unset. */
18
+ defaultReasoningEffort?: string | null;
19
+ models?: Array<{
20
+ value?: string;
21
+ label?: string;
22
+ description?: string;
23
+ efforts?: string[];
24
+ /** The effort this model starts at when none is chosen. */
25
+ defaultEffort?: string | null;
26
+ /** The harness's OWN recommendation (Codex `model/list`), not skrr's. */
27
+ isDefault?: boolean;
28
+ }>;
29
+ }
30
+ /**
31
+ * What `default` means on this harness, in lines under the table (OSK-12114).
32
+ *
33
+ * Three candidates can answer "which model runs on `default`": the harness
34
+ * row's stored default, the harness's own configuration on that machine (for
35
+ * Codex, `model` in its config.toml), and the vendor's recommendation that the
36
+ * live list marks. They win in that order, and the list alone cannot see the
37
+ * middle one — so say the order rather than imply the marked model runs.
38
+ */
39
+ export declare function describeModelDefaults(report: HarnessModelsReport | undefined): string[];
40
+ /**
41
+ * Stated under every curated answer: the list is ours, not the harness's
42
+ * admission rule. Without it `curated` reads as "these are the only choices"
43
+ * — the web card has always said this; the CLI never did.
44
+ */
45
+ export declare const CURATED_ACCEPTS_ANY_ID = "Any model id the harness itself accepts works too, even if it is not listed \u2014 set it with `skrr agents update <agent> --model <id>`.";
46
+ /**
47
+ * Why a curated answer is curated, as a clause. `codex · curated` used to
48
+ * print identically for an offline daemon, a daemon too old to probe, and a
49
+ * probe that timed out — three different next actions (OSK-12062).
50
+ */
51
+ export declare function curatedReasonClause(report: HarnessModelsReport | undefined): string;
52
+ /** One line saying where the list came from. */
53
+ export declare function describeModelSource(report: HarnessModelsReport | undefined): string;
54
+ /** What to print when the answer carries no models at all. */
55
+ export declare function describeEmptyModels(report: HarnessModelsReport | undefined): string;
2
56
  /**
3
57
  * Which models this harness can actually run.
4
58
  *
@@ -1,9 +1,95 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CURATED_ACCEPTS_ANY_ID = void 0;
4
+ exports.describeModelDefaults = describeModelDefaults;
5
+ exports.curatedReasonClause = curatedReasonClause;
6
+ exports.describeModelSource = describeModelSource;
7
+ exports.describeEmptyModels = describeEmptyModels;
3
8
  const core_1 = require("@oclif/core");
4
9
  const base_command_1 = require("../../base-command");
5
10
  const format_1 = require("../../lib/format");
6
11
  const harnesses_1 = require("../../lib/harnesses");
12
+ /**
13
+ * What `default` means on this harness, in lines under the table (OSK-12114).
14
+ *
15
+ * Three candidates can answer "which model runs on `default`": the harness
16
+ * row's stored default, the harness's own configuration on that machine (for
17
+ * Codex, `model` in its config.toml), and the vendor's recommendation that the
18
+ * live list marks. They win in that order, and the list alone cannot see the
19
+ * middle one — so say the order rather than imply the marked model runs.
20
+ */
21
+ function describeModelDefaults(report) {
22
+ const provider = report?.provider || 'the harness';
23
+ const lines = [];
24
+ const marked = (report?.models ?? []).find((m) => m.isDefault)?.value;
25
+ if (report?.defaultModel) {
26
+ lines.push(`Harness default: ${report.defaultModel} — what an agent on \`default\` runs on this harness.`);
27
+ }
28
+ else {
29
+ lines.push(`No harness default is set, so an agent on \`default\` runs the model the daemon's config ` +
30
+ `or ${provider}'s own config on that machine names` +
31
+ `${marked ? `, else ${marked} (* — ${provider}'s own recommendation)` : ''}.`);
32
+ }
33
+ if (report?.defaultReasoningEffort) {
34
+ lines.push(`Harness default effort: ${report.defaultReasoningEffort}.`);
35
+ }
36
+ lines.push('The model a turn actually ran is reported when it ends (`agents chat` prints `[run] model:`).');
37
+ return lines;
38
+ }
39
+ /**
40
+ * Stated under every curated answer: the list is ours, not the harness's
41
+ * admission rule. Without it `curated` reads as "these are the only choices"
42
+ * — the web card has always said this; the CLI never did.
43
+ */
44
+ exports.CURATED_ACCEPTS_ANY_ID = 'Any model id the harness itself accepts works too, even if it is not listed — set it with `skrr agents update <agent> --model <id>`.';
45
+ /**
46
+ * Why a curated answer is curated, as a clause. `codex · curated` used to
47
+ * print identically for an offline daemon, a daemon too old to probe, and a
48
+ * probe that timed out — three different next actions (OSK-12062).
49
+ */
50
+ function curatedReasonClause(report) {
51
+ const provider = report?.provider || 'this harness';
52
+ const detail = report?.sourceDetail ? ` (${report.sourceDetail})` : '';
53
+ switch (report?.sourceReason) {
54
+ case 'no_live_catalog_for_harness':
55
+ return `${provider} has no way to list its own models, so this list is the catalog`;
56
+ case 'daemon_offline':
57
+ return `the machine running ${provider} is offline, so its live model list could not be read`;
58
+ case 'live_catalog_unsupported':
59
+ return `the daemon on that machine does not offer a live model list — update it to get one`;
60
+ case 'live_probe_failed':
61
+ return `asking ${provider} for its live model list failed${detail}`;
62
+ case undefined:
63
+ case '':
64
+ return 'the server did not say why the live list was not used';
65
+ default:
66
+ return `live list not used: ${report?.sourceReason}${detail}`;
67
+ }
68
+ }
69
+ /** One line saying where the list came from. */
70
+ function describeModelSource(report) {
71
+ switch (report?.source) {
72
+ case 'managed':
73
+ return 'managed — the models your plan admits for this harness';
74
+ case 'live':
75
+ return `live — reported by the installed ${report.provider || 'harness'} on its machine`;
76
+ case 'curated':
77
+ return `curated — skrr's own list; ${curatedReasonClause(report)}`;
78
+ default:
79
+ return report?.source ?? 'unknown source';
80
+ }
81
+ }
82
+ /** What to print when the answer carries no models at all. */
83
+ function describeEmptyModels(report) {
84
+ const resolved = report?.provider;
85
+ if (!resolved) {
86
+ return 'This harness resolved to no provider, so no model list could be built. That is a defect, not a configuration problem — report it with the harness id.';
87
+ }
88
+ // An empty answer is always curated: a live or managed answer with no models
89
+ // degrades to curated server-side. So the reason is what explains it — for
90
+ // opencode it is usually the live probe, which the old message never named.
91
+ return `No models are listed for ${resolved}: skrr's curated list has no entries for it, and ${curatedReasonClause(report)}. ${exports.CURATED_ACCEPTS_ANY_ID}`;
92
+ }
7
93
  /**
8
94
  * Which models this harness can actually run.
9
95
  *
@@ -44,29 +130,36 @@ class HarnessesModels extends base_command_1.BaseCommand {
44
130
  // where the list comes from picks one — and, in the bug this replaced,
45
131
  // would have shown `provider` as null on a Claude harness and named the
46
132
  // defect on sight.
47
- const resolved = report?.provider;
48
- this.log(resolved
49
- ? `No models are listed for ${resolved}. skrr keeps a curated list per provider and ${resolved} has no entries in it, so pick a model on the agent instead — \`skrr agents update <agent> --model <id>\` accepts an id this list does not name.`
50
- : 'This harness resolved to no provider, so no model list could be built. That is a defect, not a configuration problem — report it with the harness id.');
133
+ this.log(describeEmptyModels(report));
51
134
  return;
52
135
  }
53
136
  if (report?.provider || report?.source) {
54
137
  // The source matters when the list is short: `curated` is our table,
55
- // anything else came from the harness itself.
56
- this.log(`${report.provider ?? 'unknown provider'} · ${report.source ?? 'unknown source'}`);
138
+ // anything else came from the harness itself — and a curated answer
139
+ // says why the harness's own list was not used.
140
+ this.log(`${report.provider ?? 'unknown provider'} · ${describeModelSource(report)}`);
141
+ if (report.source === 'curated')
142
+ this.log(exports.CURATED_ACCEPTS_ANY_ID);
57
143
  this.log('');
58
144
  }
59
145
  (0, format_1.renderTable)(models.map((m) => ({
60
- model: (0, format_1.formatCell)(m.value),
146
+ // `*` is the harness's own recommendation, which the JSON carried as
147
+ // `isDefault` and this table dropped (OSK-12114).
148
+ model: `${(0, format_1.formatCell)(m.value)}${m.isDefault ? ' *' : ''}`,
61
149
  label: (0, format_1.formatCell)(m.label),
62
150
  // An empty efforts array means the model takes no effort setting at
63
151
  // all, which is different from one we failed to read.
64
152
  efforts: m.efforts && m.efforts.length > 0 ? m.efforts.join(', ') : '-',
153
+ defaultEffort: m.defaultEffort ? String(m.defaultEffort) : '-',
65
154
  })), [
66
155
  { key: 'model', header: 'MODEL' },
67
156
  { key: 'label', header: 'LABEL', maxWidth: 26 },
68
157
  { key: 'efforts', header: 'EFFORTS' },
158
+ { key: 'defaultEffort', header: 'DEFAULT EFFORT' },
69
159
  ], (line) => this.log(line));
160
+ this.log('');
161
+ for (const line of describeModelDefaults(report))
162
+ this.log(line);
70
163
  }
71
164
  }
72
165
  exports.default = HarnessesModels;
@@ -10,6 +10,17 @@ export default class HarnessesShow extends BaseCommand {
10
10
  };
11
11
  run(): Promise<void>;
12
12
  }
13
+ /**
14
+ * Which permission modes this harness accepts, and its own default (OSK-12130).
15
+ *
16
+ * The rule used to surface only as an agent write refusing a mode, so the way
17
+ * to learn that Gemini runs only as bypassPermissions was three refusals in a
18
+ * row. Printed only when the server sent it (a local harness row).
19
+ */
20
+ export declare function harnessPermissionLines(harness: {
21
+ permissionModes?: unknown;
22
+ defaultPermissionMode?: unknown;
23
+ }): string[];
13
24
  /**
14
25
  * Say what the caller DID pass, not only what they did not.
15
26
  *
@@ -1,5 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.harnessPermissionLines = harnessPermissionLines;
3
4
  exports.describeWrongHarnessRef = describeWrongHarnessRef;
4
5
  exports.resolveHarnessRef = resolveHarnessRef;
5
6
  exports.pickHarnessRef = pickHarnessRef;
@@ -113,6 +114,8 @@ class HarnessesShow extends base_command_1.BaseCommand {
113
114
  }
114
115
  if (harness.daemonId)
115
116
  this.log(` daemon ${harness.daemonId}`);
117
+ for (const line of harnessPermissionLines(harness))
118
+ this.log(line);
116
119
  if (verdict) {
117
120
  this.log(` readiness ${verdict.label} — ${verdict.detail}`);
118
121
  }
@@ -124,6 +127,38 @@ class HarnessesShow extends base_command_1.BaseCommand {
124
127
  }
125
128
  }
126
129
  exports.default = HarnessesShow;
130
+ const PERMISSION_MODES_SOURCE_LABELS = {
131
+ daemon: 'reported by the daemon',
132
+ daemon_record: 'as the daemon last reported it',
133
+ static: "from skrr's harness table",
134
+ vocabulary: 'not declared for this harness; the daemon enforces',
135
+ };
136
+ /**
137
+ * Which permission modes this harness accepts, and its own default (OSK-12130).
138
+ *
139
+ * The rule used to surface only as an agent write refusing a mode, so the way
140
+ * to learn that Gemini runs only as bypassPermissions was three refusals in a
141
+ * row. Printed only when the server sent it (a local harness row).
142
+ */
143
+ function harnessPermissionLines(harness) {
144
+ const report = harness.permissionModes;
145
+ const modes = Array.isArray(report?.modes)
146
+ ? report.modes.filter((m) => typeof m === 'string')
147
+ : [];
148
+ if (modes.length === 0)
149
+ return [];
150
+ const source = typeof report?.source === 'string'
151
+ ? (PERMISSION_MODES_SOURCE_LABELS[report.source] ?? report.source)
152
+ : '';
153
+ const stored = typeof harness.defaultPermissionMode === 'string' && harness.defaultPermissionMode
154
+ ? harness.defaultPermissionMode
155
+ : null;
156
+ return [
157
+ ` permission accepts ${modes.join(', ')}${source ? ` (${source})` : ''}`,
158
+ ` default ${stored ??
159
+ "not set — an agent's own mode applies, else the daemon's config (`harnesses update --default-permission-mode`)"}`,
160
+ ];
161
+ }
127
162
  /**
128
163
  * Say what the caller DID pass, not only what they did not.
129
164
  *
@@ -1,7 +1,8 @@
1
1
  import { BaseCommand } from '../../base-command';
2
2
  import { getHarness } from '../../lib/harnesses';
3
3
  /**
4
- * Rename an harness, or change how Claude Code runs on it.
4
+ * Rename a harness, change how Claude Code runs on it, or set the model,
5
+ * reasoning effort and permission mode its sessions default to.
5
6
  *
6
7
  * `--name` renames it FOR EVERYONE it is shared with. `skrr harnesses alias`
7
8
  * changes only what you see; the two are different operations and the server
@@ -21,9 +22,35 @@ export default class HarnessesUpdate extends BaseCommand {
21
22
  name: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
22
23
  description: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
23
24
  'claude-engine': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
25
+ 'default-model': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
26
+ 'clear-default-model': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
27
+ 'default-effort': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
28
+ 'clear-default-effort': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
29
+ 'default-permission-mode': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
30
+ 'clear-default-permission-mode': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
24
31
  };
25
32
  run(): Promise<void>;
26
33
  }
34
+ export interface HarnessUpdateFlags {
35
+ name?: string;
36
+ description?: string;
37
+ 'claude-engine'?: string;
38
+ 'default-model'?: string;
39
+ 'clear-default-model'?: boolean;
40
+ 'default-effort'?: string;
41
+ 'clear-default-effort'?: boolean;
42
+ 'default-permission-mode'?: string;
43
+ 'clear-default-permission-mode'?: boolean;
44
+ }
45
+ /**
46
+ * Flags → the `patch` of `PATCH /api/harnesses/:id`. The clear spellings are
47
+ * the server's own: an empty string clears `defaultModel` and
48
+ * `defaultReasoningEffort`, `null` clears `defaultPermissionMode`. Clearing is
49
+ * a separate boolean flag rather than a sentinel value because `none` is a
50
+ * real reasoning effort (GPT-5.x accepts it), so `--default-effort none` has
51
+ * to mean "set it to none", not "remove it".
52
+ */
53
+ export declare function buildHarnessPatch(flags: HarnessUpdateFlags): Record<string, unknown>;
27
54
  /**
28
55
  * `--name` sets the name everyone sees. When YOUR alias covers it, you will not
29
56
  * see the new name anywhere you look, and "Updated <name>." read as though the
@@ -1,12 +1,15 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.buildHarnessPatch = buildHarnessPatch;
3
4
  exports.sharedRenameMessage = sharedRenameMessage;
4
5
  const core_1 = require("@oclif/core");
6
+ const data_provider_1 = require("@skrr-ai/data-provider");
5
7
  const base_command_1 = require("../../base-command");
6
8
  const harnesses_1 = require("../../lib/harnesses");
7
9
  const harness_labels_1 = require("../../lib/harness-labels");
8
10
  /**
9
- * Rename an harness, or change how Claude Code runs on it.
11
+ * Rename a harness, change how Claude Code runs on it, or set the model,
12
+ * reasoning effort and permission mode its sessions default to.
10
13
  *
11
14
  * `--name` renames it FOR EVERYONE it is shared with. `skrr harnesses alias`
12
15
  * changes only what you see; the two are different operations and the server
@@ -16,10 +19,13 @@ const harness_labels_1 = require("../../lib/harness-labels");
16
19
  * version first and a concurrent edit answers 409 instead of overwriting.
17
20
  */
18
21
  class HarnessesUpdate extends base_command_1.BaseCommand {
19
- static description = 'Rename a harness or set how Claude Code is driven on it';
22
+ static description = 'Rename a harness, set how Claude Code is driven on it, or set its default model, effort, and permission mode';
20
23
  static examples = [
21
24
  '<%= config.bin %> harnesses update <harness-id> --name "Studio iMac"',
22
25
  '<%= config.bin %> harnesses update <harness-id> --claude-engine sdk',
26
+ '<%= config.bin %> harnesses update <harness-id> --default-model opus --default-effort high',
27
+ '<%= config.bin %> harnesses update <harness-id> --default-permission-mode acceptEdits',
28
+ '<%= config.bin %> harnesses update <harness-id> --clear-default-model --clear-default-effort',
23
29
  ];
24
30
  static args = {
25
31
  harness: core_1.Args.string({ ignoreStdin: true, description: 'Harness ID', required: true }),
@@ -34,19 +40,45 @@ class HarnessesUpdate extends base_command_1.BaseCommand {
34
40
  description: 'How Claude Code runs on this machine: `sdk` in-process (needed for AskUserQuestion), `cli` as a child process. Local CLAUDE harnesses only — the server refuses it on any other provider, because nothing would ever read it.',
35
41
  options: ['cli', 'sdk'],
36
42
  }),
43
+ 'default-model': core_1.Flags.string({
44
+ description: "Model this harness's sessions use when the agent or task names none, in the harness's own " +
45
+ 'namespace (`opus`, `gpt-5-codex`, …). `skrr harnesses models <harness-id>` lists what it can run. ' +
46
+ 'Not allowlisted: any safe model identifier is stored as given, so check the spelling.',
47
+ exclusive: ['clear-default-model'],
48
+ }),
49
+ 'clear-default-model': core_1.Flags.boolean({
50
+ description: "Remove the default model, so the harness's own default applies",
51
+ exclusive: ['default-model'],
52
+ }),
53
+ 'default-effort': core_1.Flags.string({
54
+ description: "Reasoning effort this harness's sessions default to. Checked against the model the write leaves " +
55
+ 'in place (the new --default-model when you pass both); `skrr harnesses models <harness-id>` lists ' +
56
+ 'the efforts each model accepts. Harnesses with no effort setting refuse it.',
57
+ exclusive: ['clear-default-effort'],
58
+ }),
59
+ 'clear-default-effort': core_1.Flags.boolean({
60
+ description: "Remove the default reasoning effort, so the harness's own default applies",
61
+ exclusive: ['default-effort'],
62
+ }),
63
+ 'default-permission-mode': core_1.Flags.string({
64
+ description: "Permission mode this harness's sessions fall back to when the agent sets none. Local harnesses " +
65
+ 'only. One-shot harnesses (gemini, opencode, cursor, copilot, goose, pi, openclaw) can only run as ' +
66
+ 'bypassPermissions; any other mode is refused for them.',
67
+ options: [...data_provider_1.LOCAL_HARNESS_PERMISSION_MODES],
68
+ exclusive: ['clear-default-permission-mode'],
69
+ }),
70
+ 'clear-default-permission-mode': core_1.Flags.boolean({
71
+ description: "Remove this harness's permission-mode override, so the daemon's own configured default applies",
72
+ exclusive: ['default-permission-mode'],
73
+ }),
37
74
  };
38
75
  async run() {
39
76
  this.requireAuth();
40
77
  const { args, flags } = await this.parse(HarnessesUpdate);
41
- const patch = {};
42
- if (flags.name !== undefined)
43
- patch.name = flags.name;
44
- if (flags.description !== undefined)
45
- patch.description = flags.description;
46
- if (flags['claude-engine'] !== undefined)
47
- patch.claudeEngine = flags['claude-engine'];
78
+ const patch = buildHarnessPatch(flags);
48
79
  if (Object.keys(patch).length === 0) {
49
- this.error('Nothing to change. Pass --name, --description, or --claude-engine.', { exit: 1 });
80
+ this.error('Nothing to change. Pass --name, --description, --claude-engine, --default-model, ' +
81
+ '--default-effort, --default-permission-mode, or a --clear-default-* flag.', { exit: 1 });
50
82
  return;
51
83
  }
52
84
  try {
@@ -65,6 +97,37 @@ class HarnessesUpdate extends base_command_1.BaseCommand {
65
97
  }
66
98
  }
67
99
  exports.default = HarnessesUpdate;
100
+ /**
101
+ * Flags → the `patch` of `PATCH /api/harnesses/:id`. The clear spellings are
102
+ * the server's own: an empty string clears `defaultModel` and
103
+ * `defaultReasoningEffort`, `null` clears `defaultPermissionMode`. Clearing is
104
+ * a separate boolean flag rather than a sentinel value because `none` is a
105
+ * real reasoning effort (GPT-5.x accepts it), so `--default-effort none` has
106
+ * to mean "set it to none", not "remove it".
107
+ */
108
+ function buildHarnessPatch(flags) {
109
+ const patch = {};
110
+ if (flags.name !== undefined)
111
+ patch.name = flags.name;
112
+ if (flags.description !== undefined)
113
+ patch.description = flags.description;
114
+ if (flags['claude-engine'] !== undefined)
115
+ patch.claudeEngine = flags['claude-engine'];
116
+ if (flags['default-model'] !== undefined)
117
+ patch.defaultModel = flags['default-model'];
118
+ if (flags['clear-default-model'])
119
+ patch.defaultModel = '';
120
+ if (flags['default-effort'] !== undefined)
121
+ patch.defaultReasoningEffort = flags['default-effort'];
122
+ if (flags['clear-default-effort'])
123
+ patch.defaultReasoningEffort = '';
124
+ if (flags['default-permission-mode'] !== undefined) {
125
+ patch.defaultPermissionMode = flags['default-permission-mode'];
126
+ }
127
+ if (flags['clear-default-permission-mode'])
128
+ patch.defaultPermissionMode = null;
129
+ return patch;
130
+ }
68
131
  /**
69
132
  * `--name` sets the name everyone sees. When YOUR alias covers it, you will not
70
133
  * see the new name anywhere you look, and "Updated <name>." read as though the
@@ -39,6 +39,7 @@ const base_command_1 = require("../base-command");
39
39
  const config_1 = require("../lib/config");
40
40
  const first_party_harness_managed_1 = require("../lib/first-party-harness-managed");
41
41
  const auth_storage_1 = require("../lib/auth-storage");
42
+ const daemonBroker_1 = require("../lib/daemonBroker");
42
43
  const refresh_1 = require("../lib/refresh");
43
44
  const api_fetch_1 = require("../lib/api-fetch");
44
45
  const cred_envelope_1 = require("../lib/cred-envelope");
@@ -129,7 +130,17 @@ class Logout extends base_command_1.BaseCommand {
129
130
  hasUnreadableEncryptedToken,
130
131
  allDevices: flags['all-devices'],
131
132
  })) {
132
- this.log('Not signed in. Nothing to do.');
133
+ // On a broker-mode machine (a Dedicated guest descriptor advertising
134
+ // `access_token`) there IS no local credential to clear — the CLI holds
135
+ // a per-process token redeemed over loopback and nothing is persisted.
136
+ // Say that honestly rather than reporting a logout that did not happen.
137
+ if ((0, daemonBroker_1.findBrokeredHandoffDescriptor)()) {
138
+ this.log("This machine is signed in through the platform's managed hand-off; " +
139
+ 'there is no local credential to clear.');
140
+ }
141
+ else {
142
+ this.log('Not signed in. Nothing to do.');
143
+ }
133
144
  return;
134
145
  }
135
146
  if (flags['all-devices'] && !hasUsableLocalToken) {
@@ -40,7 +40,7 @@ class DedicatedMachines extends core_1.Command {
40
40
  ['show', 'state, size, spend, health and engines of one lease'],
41
41
  ['current', 'the lease of the machine this runs on (or pass `self`)'],
42
42
  ['attach', 'pin an agent so its work runs on the machine'],
43
- ['terminal', 'open an interactive shell on the machine'],
43
+ ['terminal', 'open a shell that survives disconnect (ls|kill|rename manage it)'],
44
44
  ['sign-in', 'sign in to GitHub CLI, Claude Code or Codex there'],
45
45
  ['exec', 'run one command; output and exit code'],
46
46
  ['cp', 'copy a file to or from the machine'],
@@ -44,7 +44,10 @@ class DedicatedMachinesSignIn extends dedicated_lease_command_1.DedicatedLeaseCo
44
44
  // Said before, not after: once the code is entered the credential already
45
45
  // sits on the workspace volume, in every later snapshot.
46
46
  process.stderr.write(`${preset.label} sign-in is stored on the workspace volume and shared by everything running as skrr-workload on this machine, including agents. Prefer a narrowly-scoped account or token.\n`);
47
- await this.openDedicatedTerminal(args.lease, flags, preset.command);
47
+ // Its own named session — not `main`: a sign-in typed into a shell that
48
+ // is running a dev server would wait forever for a prompt that is not
49
+ // coming, and the session survives a detached OAuth flow either way.
50
+ await this.openDedicatedTerminal(args.lease, { ...flags, name: 'sign-in' }, preset.command);
48
51
  }
49
52
  }
50
53
  exports.default = DedicatedMachinesSignIn;
@@ -0,0 +1,21 @@
1
+ import { DedicatedServiceCommand } from '../../../../lib/dedicated-service-command';
2
+ /**
3
+ * `skrr machines dedicated terminal kill` — end a persistent session.
4
+ *
5
+ * Detaching (Ctrl-], a dropped connection, `terminal` exiting) never ends a
6
+ * session — the machine keeps the tmux session and everything in it alive
7
+ * by design. This is the explicit verb that destroys one: the shell and its
8
+ * whole process tree stop with it.
9
+ */
10
+ export default class DedicatedMachinesTerminalKill extends DedicatedServiceCommand {
11
+ static description: string;
12
+ static examples: string[];
13
+ static args: {
14
+ lease: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
15
+ name: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
16
+ };
17
+ static flags: {
18
+ json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
19
+ };
20
+ run(): Promise<void>;
21
+ }