@skrr-ai/cli 0.1.37 → 0.1.38

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 (174) hide show
  1. package/bin/dev-fallback.js +167 -4
  2. package/dist/base-command.js +46 -2
  3. package/dist/commands/agents/actions/create.js +18 -7
  4. package/dist/commands/agents/actions/delete.d.ts +1 -0
  5. package/dist/commands/agents/actions/delete.js +7 -0
  6. package/dist/commands/agents/actions/update.js +5 -3
  7. package/dist/commands/agents/api-actions/add.js +5 -2
  8. package/dist/commands/agents/api-actions/index.d.ts +11 -0
  9. package/dist/commands/agents/api-actions/index.js +27 -1
  10. package/dist/commands/agents/avatar/default/index.d.ts +11 -0
  11. package/dist/commands/agents/avatar/default/index.js +17 -0
  12. package/dist/commands/agents/avatar/default/set.d.ts +2 -1
  13. package/dist/commands/agents/avatar/default/set.js +32 -7
  14. package/dist/commands/agents/avatar/upload.js +7 -1
  15. package/dist/commands/agents/categories/delete.js +8 -0
  16. package/dist/commands/agents/categories/index.d.ts +21 -5
  17. package/dist/commands/agents/categories/index.js +68 -15
  18. package/dist/commands/agents/chat.d.ts +21 -0
  19. package/dist/commands/agents/chat.js +94 -7
  20. package/dist/commands/agents/create.js +11 -4
  21. package/dist/commands/agents/list.d.ts +15 -0
  22. package/dist/commands/agents/list.js +31 -1
  23. package/dist/commands/agents/scene-background/upload.d.ts +30 -0
  24. package/dist/commands/agents/scene-background/upload.js +97 -0
  25. package/dist/commands/agents/show.js +13 -0
  26. package/dist/commands/agents/tools/calls.d.ts +23 -5
  27. package/dist/commands/agents/tools/calls.js +81 -18
  28. package/dist/commands/agents/update.js +15 -5
  29. package/dist/commands/code/{oversky-agent.d.ts → skrr-agent.d.ts} +3 -3
  30. package/dist/commands/code/{oversky-agent.js → skrr-agent.js} +11 -8
  31. package/dist/commands/convos/messages.d.ts +13 -0
  32. package/dist/commands/convos/messages.js +40 -16
  33. package/dist/commands/daemon/index.js +8 -1
  34. package/dist/commands/daemon/logs.d.ts +26 -0
  35. package/dist/commands/daemon/logs.js +56 -0
  36. package/dist/commands/daemon/uninstall.js +1 -1
  37. package/dist/commands/followups/act.js +3 -2
  38. package/dist/commands/followups/cancel.d.ts +6 -3
  39. package/dist/commands/followups/cancel.js +16 -6
  40. package/dist/commands/followups/observability.d.ts +23 -0
  41. package/dist/commands/followups/observability.js +114 -0
  42. package/dist/commands/followups/reschedule.js +1 -1
  43. package/dist/commands/followups/resolve.js +3 -3
  44. package/dist/commands/followups/show.js +2 -1
  45. package/dist/commands/followups/watch.js +9 -5
  46. package/dist/commands/harnesses/forget.js +28 -0
  47. package/dist/commands/harnesses/install.d.ts +41 -0
  48. package/dist/commands/harnesses/install.js +165 -0
  49. package/dist/commands/harnesses/installers.d.ts +45 -0
  50. package/dist/commands/harnesses/installers.js +155 -0
  51. package/dist/commands/harnesses/list.d.ts +82 -1
  52. package/dist/commands/harnesses/list.js +104 -12
  53. package/dist/commands/harnesses/models.js +10 -1
  54. package/dist/commands/harnesses/show.d.ts +15 -0
  55. package/dist/commands/harnesses/show.js +44 -3
  56. package/dist/commands/harnesses/update.js +1 -1
  57. package/dist/commands/harnesses/usage.d.ts +25 -0
  58. package/dist/commands/harnesses/usage.js +24 -2
  59. package/dist/commands/instructions/status.js +13 -1
  60. package/dist/commands/machines/dedicated/catalog.js +4 -15
  61. package/dist/commands/machines/dedicated/create.d.ts +8 -0
  62. package/dist/commands/machines/dedicated/create.js +58 -1
  63. package/dist/commands/machines/dedicated/destroy.js +18 -4
  64. package/dist/commands/machines/dedicated/list.js +9 -6
  65. package/dist/commands/machines/hosted/list.js +12 -0
  66. package/dist/commands/machines/list.js +11 -0
  67. package/dist/commands/machines/show.d.ts +1 -0
  68. package/dist/commands/machines/show.js +40 -11
  69. package/dist/commands/profiles/list.d.ts +23 -0
  70. package/dist/commands/profiles/list.js +96 -0
  71. package/dist/commands/search/skills.d.ts +11 -1
  72. package/dist/commands/search/skills.js +26 -8
  73. package/dist/commands/skills/details.d.ts +2 -0
  74. package/dist/commands/skills/details.js +24 -1
  75. package/dist/commands/skills/import-as-actions.d.ts +32 -0
  76. package/dist/commands/skills/import-as-actions.js +175 -0
  77. package/dist/commands/skills/install.d.ts +2 -0
  78. package/dist/commands/skills/install.js +24 -1
  79. package/dist/commands/skills/invocable.js +6 -2
  80. package/dist/commands/skills/local.d.ts +40 -0
  81. package/dist/commands/skills/local.js +160 -0
  82. package/dist/commands/skills/search.d.ts +11 -1
  83. package/dist/commands/skills/search.js +29 -8
  84. package/dist/commands/spaces/actions/add.js +23 -5
  85. package/dist/commands/spaces/actions/available.js +3 -0
  86. package/dist/commands/spaces/actions/list.d.ts +5 -0
  87. package/dist/commands/spaces/actions/list.js +49 -3
  88. package/dist/commands/spaces/actions/run.d.ts +28 -0
  89. package/dist/commands/spaces/actions/run.js +146 -0
  90. package/dist/commands/spaces/actions/update.js +27 -5
  91. package/dist/commands/spaces/label-summaries.d.ts +27 -0
  92. package/dist/commands/spaces/label-summaries.js +64 -0
  93. package/dist/commands/spaces/task-rollups.d.ts +5 -0
  94. package/dist/commands/spaces/task-rollups.js +15 -4
  95. package/dist/commands/spaces/workflows/lifecycle-diagnostics.js +63 -3
  96. package/dist/commands/tasks/actions/attach.js +32 -7
  97. package/dist/commands/tasks/complete.js +13 -0
  98. package/dist/commands/tasks/create.js +1 -1
  99. package/dist/commands/tasks/events/append.js +29 -6
  100. package/dist/commands/tasks/expectations/assess.d.ts +1 -0
  101. package/dist/commands/tasks/expectations/assess.js +43 -5
  102. package/dist/commands/tasks/expectations.js +77 -16
  103. package/dist/commands/tasks/follow-up.js +2 -2
  104. package/dist/commands/tasks/lifecycle/definitions.js +21 -2
  105. package/dist/commands/tasks/lifecycle/detach.d.ts +7 -0
  106. package/dist/commands/tasks/lifecycle/detach.js +43 -3
  107. package/dist/commands/tasks/lifecycle/migrate.d.ts +2 -0
  108. package/dist/commands/tasks/lifecycle/migrate.js +42 -3
  109. package/dist/commands/tasks/lifecycle/reopen.d.ts +2 -0
  110. package/dist/commands/tasks/lifecycle/reopen.js +40 -3
  111. package/dist/commands/tasks/lifecycle/transition.js +33 -2
  112. package/dist/commands/tasks/list.js +16 -9
  113. package/dist/commands/tasks/move.js +11 -13
  114. package/dist/commands/tasks/review-queue.d.ts +15 -0
  115. package/dist/commands/tasks/review-queue.js +180 -31
  116. package/dist/commands/tasks/show.d.ts +11 -0
  117. package/dist/commands/tasks/show.js +105 -12
  118. package/dist/commands/tasks/trust.js +7 -2
  119. package/dist/commands/tasks/update.js +46 -10
  120. package/dist/commands/tasks/updates/add.js +1 -1
  121. package/dist/commands/token/list.d.ts +22 -0
  122. package/dist/commands/token/list.js +68 -0
  123. package/dist/commands/token/revoke.d.ts +23 -0
  124. package/dist/commands/token/revoke.js +53 -0
  125. package/dist/lib/agent-actions.d.ts +57 -2
  126. package/dist/lib/agent-actions.js +113 -21
  127. package/dist/lib/agent-pin-mute.d.ts +10 -5
  128. package/dist/lib/agent-pin-mute.js +17 -4
  129. package/dist/lib/agent-visual-refs.d.ts +27 -0
  130. package/dist/lib/agent-visual-refs.js +127 -2
  131. package/dist/lib/agentic-stream.d.ts +6 -0
  132. package/dist/lib/agentic-stream.js +34 -3
  133. package/dist/lib/assignee-resolver.d.ts +1 -1
  134. package/dist/lib/assignee-resolver.js +8 -0
  135. package/dist/lib/bulk-task-targets.d.ts +12 -0
  136. package/dist/lib/bulk-task-targets.js +34 -1
  137. package/dist/lib/cli-installers.d.ts +130 -0
  138. package/dist/lib/cli-installers.js +145 -0
  139. package/dist/lib/command-miss.js +55 -7
  140. package/dist/lib/dedicated-lease-command.d.ts +13 -0
  141. package/dist/lib/dedicated-lease-command.js +22 -1
  142. package/dist/lib/dedicated-machines.d.ts +95 -1
  143. package/dist/lib/dedicated-machines.js +236 -2
  144. package/dist/lib/failed-lookup.d.ts +3 -0
  145. package/dist/lib/failed-lookup.js +40 -0
  146. package/dist/lib/first-party-harness-agent.js +9 -2
  147. package/dist/lib/first-party-harness-doctor.d.ts +15 -0
  148. package/dist/lib/first-party-harness-doctor.js +126 -27
  149. package/dist/lib/followups.d.ts +14 -6
  150. package/dist/lib/followups.js +31 -6
  151. package/dist/lib/harnesses.d.ts +8 -0
  152. package/dist/lib/instruction-input.d.ts +19 -1
  153. package/dist/lib/instruction-input.js +52 -1
  154. package/dist/lib/label-scope.d.ts +15 -0
  155. package/dist/lib/label-scope.js +25 -5
  156. package/dist/lib/local-skills.d.ts +98 -0
  157. package/dist/lib/local-skills.js +195 -0
  158. package/dist/lib/machines.d.ts +10 -10
  159. package/dist/lib/machines.js +23 -1
  160. package/dist/lib/search-query.d.ts +19 -0
  161. package/dist/lib/search-query.js +35 -0
  162. package/dist/lib/space-custom-actions.d.ts +72 -0
  163. package/dist/lib/space-custom-actions.js +117 -0
  164. package/dist/lib/space-resolver.d.ts +1 -21
  165. package/dist/lib/space-resolver.js +19 -10
  166. package/dist/lib/task-closure.d.ts +3 -2
  167. package/dist/lib/task-closure.js +18 -20
  168. package/dist/lib/task-lifecycle-output.js +117 -9
  169. package/dist/lib/task-ref-resolver.d.ts +1 -17
  170. package/dist/lib/task-ref-resolver.js +18 -8
  171. package/dist/lib/tasks.js +9 -3
  172. package/dist/node_modules/@skrr-ai/data-provider/index.js +5076 -4947
  173. package/oclif.manifest.json +26635 -25649
  174. package/package.json +9 -3
@@ -5,6 +5,7 @@ const promises_1 = require("node:fs/promises");
5
5
  const node_path_1 = require("node:path");
6
6
  const data_provider_1 = require("@skrr-ai/data-provider");
7
7
  const base_command_1 = require("../../../base-command");
8
+ const agent_visual_refs_1 = require("../../../lib/agent-visual-refs");
8
9
  class AgentsAvatarUpload extends base_command_1.BaseCommand {
9
10
  static description = 'Upload an agent avatar';
10
11
  static args = { agentId: core_1.Args.string({ required: true, ignoreStdin: true }) };
@@ -15,9 +16,14 @@ class AgentsAvatarUpload extends base_command_1.BaseCommand {
15
16
  async run() {
16
17
  this.requireAuth();
17
18
  const { args, flags } = await this.parse(AgentsAvatarUpload);
19
+ const type = (0, agent_visual_refs_1.imageUploadMimeType)(flags.file);
20
+ if (!type) {
21
+ this.error(`--file must be a .png, .jpg, .jpeg, .webp or .gif image (got ${(0, node_path_1.basename)(flags.file)}).`, { exit: 1 });
22
+ }
18
23
  const bytes = await (0, promises_1.readFile)(flags.file);
19
24
  const form = new FormData();
20
- form.append('file', new Blob([bytes]), (0, node_path_1.basename)(flags.file));
25
+ // Typed, or the route refuses it as application/octet-stream (OSK-9841).
26
+ form.append('file', new Blob([new Uint8Array(bytes)], { type }), (0, node_path_1.basename)(flags.file));
21
27
  const result = await data_provider_1.dataService.uploadAgentAvatar({ agent_id: args.agentId, formData: form });
22
28
  this.log(flags.json
23
29
  ? this.agentJson(args.agentId, result)
@@ -27,7 +27,15 @@ class AgentsCategoriesDelete extends base_command_1.BaseCommand {
27
27
  this.log(JSON.stringify(result, null, 2));
28
28
  return;
29
29
  }
30
+ const outcome = result;
30
31
  this.log(`Deleted category ${args.value}`);
32
+ // Say where its agents went, because the alternative a reader assumes is
33
+ // that they went with it — and until the server moved them, they did not go
34
+ // anywhere: they kept pointing at a category that no longer existed.
35
+ if (outcome.reassigned) {
36
+ const noun = outcome.reassigned === 1 ? 'agent' : 'agents';
37
+ this.log(`Moved ${outcome.reassigned} ${noun} to ${outcome.reassignedTo}.`);
38
+ }
31
39
  }
32
40
  }
33
41
  exports.default = AgentsCategoriesDelete;
@@ -1,12 +1,27 @@
1
1
  import { BaseCommand } from '../../../base-command';
2
+ export declare function categoryDisplayLabel(row: {
3
+ value?: unknown;
4
+ label?: unknown;
5
+ }): string;
6
+ export declare function categoryDisplayDescription(row: {
7
+ description?: unknown;
8
+ }): string;
2
9
  /**
3
10
  * The vocabulary `--category` accepts.
4
11
  *
5
- * Three reads, because they answer three different questions and the server
6
- * keeps them apart: the GLOBAL catalog with marketplace counts, the caller's
7
- * view (global + their own), and the caller's own rows alone. `--mine` is the
8
- * one most people want, so the flags name the other two rather than making the
9
- * common case the odd one out.
12
+ * The plain command used to read the GLOBAL marketplace catalog, which answers a
13
+ * different question: it listed `promoted`, `general` and `all` — three rows —
14
+ * while this account's agents actually sat in twelve categories, including the
15
+ * custom one the sibling `create` command had just minted. Two of the three were
16
+ * not categories at all but marketplace browse filters the endpoint injects. And
17
+ * `--mine`, described as a view, returned MORE than the default, so a flag that
18
+ * reads like a filter was a superset. The command's own description — "the
19
+ * categories `--category` accepts" — was true of none of it.
20
+ *
21
+ * So the default is now that vocabulary: the global categories plus your own,
22
+ * without the browse filters. The marketplace view, with its counts and filters,
23
+ * is `--marketplace`, named for what it is. `--mine` still works, as the default
24
+ * it always should have been, so nothing that scripted it breaks.
10
25
  */
11
26
  export default class AgentsCategoriesList extends BaseCommand {
12
27
  static description: string;
@@ -14,6 +29,7 @@ export default class AgentsCategoriesList extends BaseCommand {
14
29
  static flags: {
15
30
  mine: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
16
31
  'custom-only': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
32
+ marketplace: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
17
33
  json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
18
34
  };
19
35
  run(): Promise<void>;
@@ -1,33 +1,80 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.categoryDisplayLabel = categoryDisplayLabel;
4
+ exports.categoryDisplayDescription = categoryDisplayDescription;
3
5
  const core_1 = require("@oclif/core");
4
6
  const data_provider_1 = require("@skrr-ai/data-provider");
5
7
  const base_command_1 = require("../../../base-command");
6
8
  const format_1 = require("../../../lib/format");
9
+ /**
10
+ * Global default categories are seeded with LOCALIZATION KEYS as their label and
11
+ * description (`com_agents_category_general`), because the web translates them.
12
+ * The CLI has no translations and printed the keys: the category most agents are
13
+ * in read `com_agents_category_general` / `com_agents_category_general_description`,
14
+ * in text and `--json` alike.
15
+ *
16
+ * Copying the translations here would be a second source that drifts. A key
17
+ * label renders as the title-cased value instead — for `general`, the one global
18
+ * default left, that is exactly its English translation — and a key description
19
+ * renders as nothing rather than as a key. `--json` keeps the raw row; only the
20
+ * human rendering is touched.
21
+ */
22
+ const LOCALIZATION_KEY = /^com_[a-z0-9_]+$/;
23
+ function categoryDisplayLabel(row) {
24
+ const label = typeof row.label === 'string' ? row.label : '';
25
+ if (label && !LOCALIZATION_KEY.test(label))
26
+ return label;
27
+ const value = typeof row.value === 'string' ? row.value : '';
28
+ if (!value)
29
+ return label || '-';
30
+ return value
31
+ .split(/[-_\s]+/)
32
+ .filter(Boolean)
33
+ .map((word) => word[0].toUpperCase() + word.slice(1))
34
+ .join(' ');
35
+ }
36
+ function categoryDisplayDescription(row) {
37
+ const description = typeof row.description === 'string' ? row.description : '';
38
+ return LOCALIZATION_KEY.test(description) ? '' : description;
39
+ }
7
40
  /**
8
41
  * The vocabulary `--category` accepts.
9
42
  *
10
- * Three reads, because they answer three different questions and the server
11
- * keeps them apart: the GLOBAL catalog with marketplace counts, the caller's
12
- * view (global + their own), and the caller's own rows alone. `--mine` is the
13
- * one most people want, so the flags name the other two rather than making the
14
- * common case the odd one out.
43
+ * The plain command used to read the GLOBAL marketplace catalog, which answers a
44
+ * different question: it listed `promoted`, `general` and `all` — three rows —
45
+ * while this account's agents actually sat in twelve categories, including the
46
+ * custom one the sibling `create` command had just minted. Two of the three were
47
+ * not categories at all but marketplace browse filters the endpoint injects. And
48
+ * `--mine`, described as a view, returned MORE than the default, so a flag that
49
+ * reads like a filter was a superset. The command's own description — "the
50
+ * categories `--category` accepts" — was true of none of it.
51
+ *
52
+ * So the default is now that vocabulary: the global categories plus your own,
53
+ * without the browse filters. The marketplace view, with its counts and filters,
54
+ * is `--marketplace`, named for what it is. `--mine` still works, as the default
55
+ * it always should have been, so nothing that scripted it breaks.
15
56
  */
16
57
  class AgentsCategoriesList extends base_command_1.BaseCommand {
17
58
  static description = 'List the agent categories `--category` accepts';
18
59
  static examples = [
19
60
  '<%= config.bin %> agents categories',
20
- '<%= config.bin %> agents categories --mine',
21
61
  '<%= config.bin %> agents categories --custom-only --json',
62
+ '<%= config.bin %> agents categories --marketplace',
22
63
  ];
23
64
  static flags = {
24
65
  mine: core_1.Flags.boolean({
25
- description: 'Your view: the global catalog plus your own custom categories',
26
- exclusive: ['custom-only'],
66
+ description: 'Deprecated: the default already lists your categories',
67
+ exclusive: ['custom-only', 'marketplace'],
68
+ hidden: true,
27
69
  }),
28
70
  'custom-only': core_1.Flags.boolean({
29
71
  description: 'Only the custom categories you created',
30
- exclusive: ['mine'],
72
+ exclusive: ['mine', 'marketplace'],
73
+ }),
74
+ marketplace: core_1.Flags.boolean({
75
+ description: 'The global marketplace catalog with agent counts, including its browse filters ' +
76
+ '(`promoted`, `all`) — not a list of values `--category` takes',
77
+ exclusive: ['mine', 'custom-only'],
31
78
  }),
32
79
  json: core_1.Flags.boolean({ description: 'Output as JSON' }),
33
80
  };
@@ -39,11 +86,11 @@ class AgentsCategoriesList extends base_command_1.BaseCommand {
39
86
  if (flags['custom-only']) {
40
87
  rows = (await data_provider_1.dataService.getUserCustomCategories());
41
88
  }
42
- else if (flags.mine) {
43
- rows = (await data_provider_1.dataService.getUserAgentCategories());
89
+ else if (flags.marketplace) {
90
+ rows = (await data_provider_1.dataService.getAgentCategories());
44
91
  }
45
92
  else {
46
- rows = (await data_provider_1.dataService.getAgentCategories());
93
+ rows = (await data_provider_1.dataService.getUserAgentCategories()).filter((row) => !(0, data_provider_1.isAgentCategoryBrowseFilter)(row.value));
47
94
  }
48
95
  }
49
96
  catch (err) {
@@ -62,12 +109,12 @@ class AgentsCategoriesList extends base_command_1.BaseCommand {
62
109
  }
63
110
  (0, format_1.renderTable)(items.map((row) => ({
64
111
  value: String(row.value ?? '-'),
65
- label: String(row.label ?? '-'),
112
+ label: categoryDisplayLabel(row),
66
113
  custom: row.custom ? 'yes' : 'no',
67
114
  count: row.count === undefined
68
115
  ? '-'
69
116
  : String(row.count),
70
- description: String(row.description ?? ''),
117
+ description: categoryDisplayDescription(row),
71
118
  })), [
72
119
  { key: 'value', header: 'VALUE', verbatim: true },
73
120
  { key: 'label', header: 'LABEL', maxWidth: 28 },
@@ -76,7 +123,13 @@ class AgentsCategoriesList extends base_command_1.BaseCommand {
76
123
  { key: 'description', header: 'DESCRIPTION', maxWidth: 48 },
77
124
  ], (line) => this.log(line));
78
125
  this.log('');
79
- this.log('VALUE is what `skrr agents create --category <value>` takes.');
126
+ if (flags.marketplace) {
127
+ this.log('Marketplace view. `promoted` and `all` are browse filters, not categories — ' +
128
+ 'run without --marketplace for the values `--category` takes.');
129
+ }
130
+ else {
131
+ this.log('VALUE is what `skrr agents create --category <value>` takes.');
132
+ }
80
133
  }
81
134
  }
82
135
  exports.default = AgentsCategoriesList;
@@ -24,6 +24,18 @@ export declare const DETACH_EXIT_CODE = 130;
24
24
  * answers true.
25
25
  */
26
26
  export declare const STREAM_LOST_EXIT_CODE = 75;
27
+ /**
28
+ * Exit code for a streamed run that reached its terminal state: 1 when it
29
+ * failed, 0 otherwise.
30
+ *
31
+ * The streaming modes share this line. `--jsonl` used to print `run.result
32
+ * {status: "failed"}` and exit 0 while the human mode exited 1 for the same run
33
+ * — and `--jsonl` is the mode a script branches on (OSK-9779). Empty text is not
34
+ * a failure here, unlike `--no-stream`: a turn that only ran tools is a success.
35
+ */
36
+ export declare function streamedResultExitCode(result: {
37
+ status?: string;
38
+ }): number;
27
39
  /**
28
40
  * What to say when the user pressed Ctrl-C and the run did not stop.
29
41
  *
@@ -124,6 +136,8 @@ export default class AgentsChat extends BaseCommand {
124
136
  jsonl: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
125
137
  json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
126
138
  'approve-tools': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
139
+ 'local-skill': import("@oclif/core/lib/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
140
+ daemon: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
127
141
  task: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
128
142
  };
129
143
  /**
@@ -138,5 +152,12 @@ export default class AgentsChat extends BaseCommand {
138
152
  * mid-run costs a session.
139
153
  */
140
154
  private static readonly TASK_REF_IN_PROMPT;
155
+ /**
156
+ * `--local-skill` names → the references the server resolves, from the daemon
157
+ * that will run the turn. Any name that does not resolve stops the command:
158
+ * the server fails a turn whose picked skill did not load, so sending it
159
+ * would only move the same error later (OSK-9478).
160
+ */
161
+ private resolveLocalSkills;
141
162
  run(): Promise<void>;
142
163
  }
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.STREAM_LOST_EXIT_CODE = exports.DETACH_EXIT_CODE = void 0;
4
+ exports.streamedResultExitCode = streamedResultExitCode;
4
5
  exports.interruptedDetachMessage = interruptedDetachMessage;
5
6
  exports.reconcileTerminalFromMessages = reconcileTerminalFromMessages;
6
7
  exports.inspectTurnCommand = inspectTurnCommand;
@@ -13,6 +14,7 @@ const data_provider_1 = require("@skrr-ai/data-provider");
13
14
  const base_command_1 = require("../../base-command");
14
15
  const agentic_stream_1 = require("../../lib/agentic-stream");
15
16
  const web_url_1 = require("../../lib/web-url");
17
+ const local_skills_1 = require("../../lib/local-skills");
16
18
  /**
17
19
  * Exit code for a run the USER stopped watching, by pressing Ctrl-C.
18
20
  *
@@ -37,6 +39,18 @@ exports.DETACH_EXIT_CODE = 130;
37
39
  * answers true.
38
40
  */
39
41
  exports.STREAM_LOST_EXIT_CODE = 75;
42
+ /**
43
+ * Exit code for a streamed run that reached its terminal state: 1 when it
44
+ * failed, 0 otherwise.
45
+ *
46
+ * The streaming modes share this line. `--jsonl` used to print `run.result
47
+ * {status: "failed"}` and exit 0 while the human mode exited 1 for the same run
48
+ * — and `--jsonl` is the mode a script branches on (OSK-9779). Empty text is not
49
+ * a failure here, unlike `--no-stream`: a turn that only ran tools is a success.
50
+ */
51
+ function streamedResultExitCode(result) {
52
+ return result.status === 'failed' ? 1 : 0;
53
+ }
40
54
  /**
41
55
  * What to say when the user pressed Ctrl-C and the run did not stop.
42
56
  *
@@ -84,18 +98,17 @@ function reconcileTerminalFromMessages(messages, responseMessageId) {
84
98
  [...messages].reverse().find((m) => m && m.isCreatedByUser === false);
85
99
  if (!row)
86
100
  return null;
87
- const agentic = row.metadata?.agentic;
88
- const recordedError = typeof agentic?.error === 'string' ? agentic.error : undefined;
89
- const text = typeof row.text === 'string' ? row.text : '';
90
- if (row.error === true) {
101
+ const failure = (0, data_provider_1.resolveMessageFailure)(row);
102
+ if (failure) {
91
103
  return {
92
104
  status: 'failed',
93
- ...(text ? { text } : {}),
105
+ ...(failure.answer ? { text: failure.answer } : {}),
94
106
  // The reason exists on the row; it simply never reached the surface. Say
95
107
  // it, rather than the transport error that masked it (OSK-7595).
96
- error: recordedError ?? 'The run failed. No reason was recorded on the message.',
108
+ error: failure.reason ?? 'The run failed. No reason was recorded on the message.',
97
109
  };
98
110
  }
111
+ const text = typeof row.text === 'string' ? row.text : '';
99
112
  // `unfinished: false` is the persister's terminal marker; an empty body with
100
113
  // it set is a turn that ended having produced nothing, which is still
101
114
  // terminal and still better reported than waited on.
@@ -183,6 +196,7 @@ class AgentsChat extends base_command_1.BaseCommand {
183
196
  '<%= config.bin %> agents chat <agent-id> --prompt "Deploy the fix" --approve-tools',
184
197
  '<%= config.bin %> agents chat <agent-id> --prompt "Summarize" --jsonl',
185
198
  '<%= config.bin %> agents chat <agent-id> --prompt "Summarize" --no-stream --json',
199
+ '<%= config.bin %> agents chat <agent-id> --prompt "Review this diff" --local-skill code-review',
186
200
  ];
187
201
  static args = {
188
202
  agentId: core_1.Args.string({ description: 'Agent ID', required: true, ignoreStdin: true }),
@@ -222,6 +236,16 @@ class AgentsChat extends base_command_1.BaseCommand {
222
236
  'approve-tools': core_1.Flags.boolean({
223
237
  description: 'Prompt interactively before approving tool calls',
224
238
  }),
239
+ 'local-skill': core_1.Flags.string({
240
+ description: 'Invoke a skill on your machine on this turn, as `skrr skills local` lists it: its ' +
241
+ 'name, or <root>/<name> when two roots share one. The turn runs on that machine. ' +
242
+ 'Repeatable.',
243
+ multiple: true,
244
+ }),
245
+ daemon: core_1.Flags.string({
246
+ description: 'The machine whose skills --local-skill names. Defaults to your only connected daemon.',
247
+ dependsOn: ['local-skill'],
248
+ }),
225
249
  task: core_1.Flags.string({
226
250
  description: 'Bind this turn to a task (UUID or OSK-####). Injects the canonical task id, ' +
227
251
  'identifier, space and completion instructions into the turn. This is a CHAT ' +
@@ -241,6 +265,49 @@ class AgentsChat extends base_command_1.BaseCommand {
241
265
  * mid-run costs a session.
242
266
  */
243
267
  static TASK_REF_IN_PROMPT = /\b(?:OSK-\d+|[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\b/;
268
+ /**
269
+ * `--local-skill` names → the references the server resolves, from the daemon
270
+ * that will run the turn. Any name that does not resolve stops the command:
271
+ * the server fails a turn whose picked skill did not load, so sending it
272
+ * would only move the same error later (OSK-9478).
273
+ */
274
+ async resolveLocalSkills(requested, daemonFlag) {
275
+ if (!requested?.length)
276
+ return null;
277
+ let daemonId = daemonFlag;
278
+ if (!daemonId) {
279
+ let daemons;
280
+ try {
281
+ daemons = await data_provider_1.dataService.getDaemons();
282
+ }
283
+ catch (err) {
284
+ this.handleApiError(err);
285
+ }
286
+ const choice = (0, local_skills_1.chooseDaemon)(daemons.active ?? []);
287
+ if (!('daemonId' in choice)) {
288
+ const listed = choice.candidates.map((d) => ` --daemon ${d.daemonId}${d.deviceName ? ` (${d.deviceName})` : ''}`);
289
+ this.error([choice.problem, ...listed].join('\n'), { exit: 2 });
290
+ }
291
+ daemonId = choice.daemonId;
292
+ }
293
+ let inventory;
294
+ try {
295
+ inventory = await data_provider_1.dataService.getDaemonSkills(daemonId);
296
+ }
297
+ catch (err) {
298
+ this.handleApiError(err);
299
+ }
300
+ if (!inventory.online) {
301
+ this.error(`${daemonId} is not connected, so no skill on it can load. Start it and retry.`, {
302
+ exit: 2,
303
+ });
304
+ }
305
+ const { refs, problems } = (0, local_skills_1.resolveLocalSkillRefs)(inventory.skills ?? [], requested);
306
+ if (problems.length > 0) {
307
+ this.error(problems.join('\n'), { exit: 2 });
308
+ }
309
+ return { daemonId, refs };
310
+ }
244
311
  async run() {
245
312
  this.requireAuth();
246
313
  const { args, flags } = await this.parse(AgentsChat);
@@ -258,6 +325,10 @@ class AgentsChat extends base_command_1.BaseCommand {
258
325
  'response and streams no events. Use --quick --json for a machine-readable ' +
259
326
  'one-shot, or drop --quick for a real streamed turn.', { exit: 2 });
260
327
  }
328
+ if (flags.quick && flags['local-skill']?.length) {
329
+ this.error('--local-skill has no meaning with --quick: the persona-only summarizer runs no ' +
330
+ 'runtime, so no skill on your machine can load. Drop --quick for a real turn.', { exit: 2 });
331
+ }
261
332
  if (flags['approve-tools'] && !process.stdin.isTTY) {
262
333
  this.error('--approve-tools requires an interactive terminal.', { exit: 2 });
263
334
  }
@@ -371,13 +442,28 @@ class AgentsChat extends base_command_1.BaseCommand {
371
442
  const contextParts = [taskContextBlock, flags.context ? `Context: ${flags.context}` : null]
372
443
  .filter(Boolean)
373
444
  .join('\n\n');
445
+ const localSkills = await this.resolveLocalSkills(flags['local-skill'], flags.daemon);
446
+ const prompt = localSkills
447
+ ? (0, local_skills_1.withSkillInvocations)(flags.prompt, localSkills.refs)
448
+ : flags.prompt;
374
449
  let started;
375
450
  try {
376
451
  started = await data_provider_1.dataService.startAgenticChat({
377
452
  agentId: args.agentId,
378
- text: contextParts ? `${contextParts}\n\n${flags.prompt}` : flags.prompt,
453
+ text: contextParts ? `${contextParts}\n\n${prompt}` : prompt,
379
454
  conversationId: threadConversationId,
380
455
  workspaceId: process.env.OVERSKY_WORKSPACE_ID,
456
+ // The server resolves each ref against the session's daemon, so the
457
+ // turn is asked to run on the machine that listed them. An agent whose
458
+ // execution policy places it elsewhere keeps that placement, and the
459
+ // run fails saying why rather than running without the skill.
460
+ ...(localSkills
461
+ ? {
462
+ skillRefs: localSkills.refs,
463
+ executionEnvironment: 'local',
464
+ preferredDaemonId: localSkills.daemonId,
465
+ }
466
+ : {}),
381
467
  });
382
468
  }
383
469
  catch (err) {
@@ -573,6 +659,7 @@ class AgentsChat extends base_command_1.BaseCommand {
573
659
  // makes shell consumers able to find the final text without replaying
574
660
  // the event reducer.
575
661
  (0, agentic_stream_1.writeJsonLine)(process.stdout, { type: 'run.result', ...result });
662
+ process.exitCode = streamedResultExitCode(result);
576
663
  }
577
664
  else if (quiet && flags.json) {
578
665
  // A real executed turn — the metadata says so, unlike `--quick`.
@@ -6,6 +6,7 @@ exports.unappliedPayloadKeys = unappliedPayloadKeys;
6
6
  exports.backgroundLoopLines = backgroundLoopLines;
7
7
  const core_1 = require("@oclif/core");
8
8
  const data_provider_1 = require("@skrr-ai/data-provider");
9
+ const agent_visual_refs_1 = require("../../lib/agent-visual-refs");
9
10
  const base_command_1 = require("../../base-command");
10
11
  const execution_target_1 = require("../../lib/execution-target");
11
12
  const prompt_1 = require("../../lib/prompt");
@@ -195,10 +196,7 @@ class AgentsCreate extends base_command_1.BaseCommand {
195
196
  'network-agent-ids': core_1.Flags.string({ description: 'Network agent IDs JSON array' }),
196
197
  'network-include-public': core_1.Flags.boolean({ description: 'Include public agents in network' }),
197
198
  'character-3d-id': core_1.Flags.string({ description: '3D character ID' }),
198
- 'scene-background': core_1.Flags.string({
199
- description: 'Backdrop for the 3D view: default, preset:<id>, or image:<filepath>@<source>. ' +
200
- 'A bare string is refused — the stored field is a typed reference, not a name.',
201
- }),
199
+ 'scene-background': core_1.Flags.string({ description: agent_visual_refs_1.SCENE_BACKGROUND_FLAG_HELP }),
202
200
  'tone-mode': core_1.Flags.string({ description: 'Tone mode' }),
203
201
  'tone-id': core_1.Flags.string({ description: 'Tone ID' }),
204
202
  'tone-notes': core_1.Flags.string({ description: 'Per-agent tone notes' }),
@@ -384,6 +382,15 @@ class AgentsCreate extends base_command_1.BaseCommand {
384
382
  if (appliedDefaults.length > 0) {
385
383
  this.log(`Defaults applied: ${appliedDefaults.join(', ')}`);
386
384
  }
385
+ // The face is a default too, and the one people most often notice later:
386
+ // eight agents created in a row all came out faceless, nothing said a policy
387
+ // had decided that, and the natural conclusion was that the random default
388
+ // did not work. When `--avatar` was not given, say what the agent got and
389
+ // what decided it.
390
+ if (!flags.avatar) {
391
+ this.log(`Avatar: ${(0, agent_visual_refs_1.agentAvatarLine)(a.avatar)}`);
392
+ this.log(' Decided by your default-avatar policy — `skrr agents avatar default` shows or changes it.');
393
+ }
387
394
  for (const line of backgroundLoopLines(agentId, flags['no-relationship-loops'] === true)) {
388
395
  this.log(line);
389
396
  }
@@ -7,6 +7,21 @@ import { BaseCommand } from '../../base-command';
7
7
  * arrives populated instead of scalar would otherwise print `[object Object]`
8
8
  * (OSK-2044), and a missing one would print the literal `undefined`.
9
9
  */
10
+ /**
11
+ * Pinned agents first, as the web sidebar orders them — the rest in the order
12
+ * the server returned.
13
+ *
14
+ * `skrr agents pin` says it pins an agent "to the top of your list", and this
15
+ * list never read pins: an agent sat at row 17 of 200 pinned or not, and nothing
16
+ * in `agents list` or `agents show` said it was pinned at all. The only way to
17
+ * see the effect of the command was `agents preferences show`, which omits the
18
+ * field entirely until the first pin.
19
+ */
20
+ export declare function orderPinnedFirst<T extends Record<string, unknown>>(items: readonly T[], preferences: Readonly<Record<string, {
21
+ isPinned?: boolean;
22
+ } | undefined>>): Array<T & {
23
+ pinned: boolean;
24
+ }>;
10
25
  export declare function agentRow(a: Record<string, unknown>): {
11
26
  id: string;
12
27
  name: string;
@@ -1,5 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.orderPinnedFirst = orderPinnedFirst;
3
4
  exports.agentRow = agentRow;
4
5
  exports.noAgentsEmptyMessage = noAgentsEmptyMessage;
5
6
  const core_1 = require("@oclif/core");
@@ -15,6 +16,25 @@ const web_url_1 = require("../../lib/web-url");
15
16
  * arrives populated instead of scalar would otherwise print `[object Object]`
16
17
  * (OSK-2044), and a missing one would print the literal `undefined`.
17
18
  */
19
+ /**
20
+ * Pinned agents first, as the web sidebar orders them — the rest in the order
21
+ * the server returned.
22
+ *
23
+ * `skrr agents pin` says it pins an agent "to the top of your list", and this
24
+ * list never read pins: an agent sat at row 17 of 200 pinned or not, and nothing
25
+ * in `agents list` or `agents show` said it was pinned at all. The only way to
26
+ * see the effect of the command was `agents preferences show`, which omits the
27
+ * field entirely until the first pin.
28
+ */
29
+ function orderPinnedFirst(items, preferences) {
30
+ return items
31
+ .map((item, index) => {
32
+ const id = String(item.id ?? item._id ?? '');
33
+ return { item: { ...item, pinned: Boolean(preferences[id]?.isPinned) }, index };
34
+ })
35
+ .sort((a, b) => Number(b.item.pinned) - Number(a.item.pinned) || a.index - b.index)
36
+ .map(({ item }) => item);
37
+ }
18
38
  function agentRow(a) {
19
39
  return {
20
40
  id: (0, format_1.formatCell)(a.id ?? a._id),
@@ -76,11 +96,21 @@ class AgentsList extends base_command_1.BaseCommand {
76
96
  this.log(noAgentsEmptyMessage(this.config.bin));
77
97
  return;
78
98
  }
79
- const rows = items.map((item) => ({
99
+ // Best-effort: a list that cannot read pins still lists, unordered by them.
100
+ let preferences = {};
101
+ try {
102
+ preferences = (await data_provider_1.dataService.getAllAgentPreferences())?.agentPreferences ?? {};
103
+ }
104
+ catch {
105
+ preferences = {};
106
+ }
107
+ const rows = orderPinnedFirst(items, preferences).map((item) => ({
108
+ pinned: item.pinned ? 'pinned' : '',
80
109
  ...agentRow(item),
81
110
  url: (0, web_url_1.agentUrlFrom)(this.cliConfig.baseURL, item) ?? '-',
82
111
  }));
83
112
  (0, format_1.renderTable)(rows, [
113
+ { key: 'pinned', header: 'PINNED' },
84
114
  { key: 'id', header: 'ID' },
85
115
  { key: 'name', header: 'NAME', maxWidth: 30 },
86
116
  { key: 'description', header: 'DESCRIPTION', maxWidth: 60 },
@@ -0,0 +1,30 @@
1
+ import { BaseCommand } from '../../../base-command';
2
+ /**
3
+ * Upload an image as an agent's 3D backdrop.
4
+ *
5
+ * `--scene-background image:<filepath>@<source>` was documented and had no value
6
+ * that could succeed from the CLI. The server accepts an image backdrop only from
7
+ * the writer's own WALLPAPER upload namespace (`/images/<userId>/wallpaper-…`),
8
+ * and the only thing that writes there is `POST /api/files/images/wallpaper` —
9
+ * which nothing in `skrr` called. Every attempt ended in
10
+ * "Scene background image must be one you uploaded", and the flag's word
11
+ * `<filepath>` read like a path on disk, which sent people looking in the wrong
12
+ * place.
13
+ *
14
+ * This is that upload, followed by setting it, so the documented form finally
15
+ * has a producer. `--upload-only` prints the `--scene-background` value instead,
16
+ * for scripts that set it later or on several agents.
17
+ */
18
+ export default class AgentsSceneBackgroundUpload extends BaseCommand {
19
+ static description: string;
20
+ static examples: string[];
21
+ static args: {
22
+ agentId: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
23
+ };
24
+ static flags: {
25
+ file: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
26
+ 'upload-only': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
27
+ json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
28
+ };
29
+ run(): Promise<void>;
30
+ }