@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
@@ -0,0 +1,97 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const core_1 = require("@oclif/core");
4
+ const promises_1 = require("node:fs/promises");
5
+ const node_path_1 = require("node:path");
6
+ const data_provider_1 = require("@skrr-ai/data-provider");
7
+ const base_command_1 = require("../../../base-command");
8
+ const agent_visual_refs_1 = require("../../../lib/agent-visual-refs");
9
+ /**
10
+ * Upload an image as an agent's 3D backdrop.
11
+ *
12
+ * `--scene-background image:<filepath>@<source>` was documented and had no value
13
+ * that could succeed from the CLI. The server accepts an image backdrop only from
14
+ * the writer's own WALLPAPER upload namespace (`/images/<userId>/wallpaper-…`),
15
+ * and the only thing that writes there is `POST /api/files/images/wallpaper` —
16
+ * which nothing in `skrr` called. Every attempt ended in
17
+ * "Scene background image must be one you uploaded", and the flag's word
18
+ * `<filepath>` read like a path on disk, which sent people looking in the wrong
19
+ * place.
20
+ *
21
+ * This is that upload, followed by setting it, so the documented form finally
22
+ * has a producer. `--upload-only` prints the `--scene-background` value instead,
23
+ * for scripts that set it later or on several agents.
24
+ */
25
+ class AgentsSceneBackgroundUpload extends base_command_1.BaseCommand {
26
+ static description = "Upload an image as an agent's 3D backdrop and set it";
27
+ static examples = [
28
+ '<%= config.bin %> agents scene-background upload agent_abc --file ./room.png',
29
+ '<%= config.bin %> agents scene-background upload agent_abc --file ./room.jpg --upload-only',
30
+ ];
31
+ static args = {
32
+ agentId: core_1.Args.string({ description: 'Agent ID', required: true, ignoreStdin: true }),
33
+ };
34
+ static flags = {
35
+ file: core_1.Flags.string({
36
+ description: 'Image to upload: PNG, JPEG, WebP or GIF (not SVG). Resized to at most 2560px wide.',
37
+ required: true,
38
+ }),
39
+ 'upload-only': core_1.Flags.boolean({
40
+ description: 'Upload, then print the --scene-background value without changing the agent',
41
+ }),
42
+ json: core_1.Flags.boolean({ description: 'Output JSON' }),
43
+ };
44
+ async run() {
45
+ this.requireAuth();
46
+ const { args, flags } = await this.parse(AgentsSceneBackgroundUpload);
47
+ // The server gates on the part's MIME type, so an untyped Blob would be
48
+ // refused as "Unsupported file type" whatever its bytes were.
49
+ const type = (0, agent_visual_refs_1.imageUploadMimeType)(flags.file);
50
+ if (!type) {
51
+ this.error(`--file must be a .png, .jpg, .jpeg, .webp or .gif image (got ${(0, node_path_1.basename)(flags.file)}).`, { exit: 1 });
52
+ }
53
+ let bytes;
54
+ try {
55
+ bytes = await (0, promises_1.readFile)(flags.file);
56
+ }
57
+ catch (err) {
58
+ this.error(`Could not read --file ${flags.file}: ${err.message}`, { exit: 1 });
59
+ }
60
+ const form = new FormData();
61
+ form.append('file', new Blob([new Uint8Array(bytes)], { type }), (0, node_path_1.basename)(flags.file));
62
+ let uploaded;
63
+ try {
64
+ uploaded = (await data_provider_1.dataService.uploadWallpaper(form));
65
+ }
66
+ catch (err) {
67
+ this.handleApiError(err);
68
+ }
69
+ const sceneBackground = {
70
+ type: 'image',
71
+ filepath: uploaded.filepath,
72
+ source: uploaded.source,
73
+ };
74
+ const flagValue = `image:${sceneBackground.filepath}@${sceneBackground.source}`;
75
+ if (!flags['upload-only']) {
76
+ try {
77
+ await data_provider_1.dataService.updateAgent({
78
+ agent_id: args.agentId,
79
+ data: { sceneBackground },
80
+ });
81
+ }
82
+ catch (err) {
83
+ this.handleApiError(err);
84
+ }
85
+ }
86
+ if (flags.json) {
87
+ this.log(JSON.stringify({ agentId: args.agentId, sceneBackground, flagValue, applied: !flags['upload-only'] }, null, 2));
88
+ return;
89
+ }
90
+ this.log(flags['upload-only']
91
+ ? `Uploaded. Set it with: --scene-background ${flagValue}`
92
+ : `Backdrop for agent ${args.agentId} is now the uploaded image.`);
93
+ if (!flags['upload-only'])
94
+ this.printAgentUrl(args.agentId);
95
+ }
96
+ }
97
+ exports.default = AgentsSceneBackgroundUpload;
@@ -8,6 +8,7 @@ const base_command_1 = require("../../base-command");
8
8
  const harness_provider_input_1 = require("../../lib/harness-provider-input");
9
9
  const web_url_1 = require("../../lib/web-url");
10
10
  const hosted_machines_1 = require("../../lib/hosted-machines");
11
+ const agent_visual_refs_1 = require("../../lib/agent-visual-refs");
11
12
  /**
12
13
  * OSK-4901 — the tone line for `agents show` text output.
13
14
  *
@@ -139,6 +140,18 @@ class AgentsShow extends base_command_1.BaseCommand {
139
140
  this.log(`Provider: ${a.provider ?? '-'}`);
140
141
  this.log(`Model: ${a.model ?? '-'}`);
141
142
  this.log(`Tone: ${agentToneLine(a)}`);
143
+ this.log(`Avatar: ${(0, agent_visual_refs_1.agentAvatarLine)(a.avatar)}`);
144
+ this.log(`Backdrop: ${(0, agent_visual_refs_1.agentSceneBackgroundLine)(a.sceneBackground)}`);
145
+ // Pin and mute are per-user, so they live in preferences rather than on the
146
+ // agent — which is why this view never showed either. Best-effort read.
147
+ try {
148
+ const prefs = (await data_provider_1.dataService.getAgentPreferences(String(a.id ?? args.id)))?.preferences;
149
+ this.log(`Pinned: ${prefs?.isPinned ? 'yes' : 'no'}`);
150
+ this.log(`Muted: ${prefs?.isMuted ? 'yes' : 'no'}`);
151
+ }
152
+ catch {
153
+ /* preferences unreadable — omit rather than claim "no" */
154
+ }
142
155
  this.log(`Category: ${a.category ?? '-'}`);
143
156
  this.log(`Author: ${a.author ?? '-'}`);
144
157
  // A cap the user cannot see is a cap they cannot diagnose: a saturated
@@ -1,12 +1,29 @@
1
1
  import { BaseCommand } from '../../../base-command';
2
+ type ToolCallRow = {
3
+ messageId: string;
4
+ toolUseId: string;
5
+ name: string;
6
+ args: string;
7
+ output: string;
8
+ complete: boolean;
9
+ source: 'message' | 'ledger';
10
+ };
2
11
  /**
3
- * Tool calls recorded in one conversation.
12
+ * The tool calls in a conversation, read from the conversation's own messages.
4
13
  *
5
- * `conversationId` is REQUIRED by the endpoint, not optional with a default, so
6
- * it is a required flag rather than something this command guesses — a
7
- * conversation-less listing would silently be somebody else's transcript or
8
- * nothing at all.
14
+ * This command read `GET /api/agents/tools/calls`, the `ToolCall` ledger — and
15
+ * nothing in the codebase writes that ledger: `createToolCall` has no caller. So
16
+ * it answered "No tool calls recorded" for EVERY conversation, including one where
17
+ * the agent had just run a Bash command, and for a conversation id that did not
18
+ * exist at all. Nobody could tell a broken command from an empty answer.
19
+ *
20
+ * Every agent turn records its tool calls as `tool_call` content parts on the
21
+ * assistant message (name, arguments, output), so that is the primary source. A
22
+ * conversation that does not exist is a NOT_FOUND, not an empty list. Ledger rows
23
+ * are still read and merged, so the endpoint stays reachable and a future writer
24
+ * is not silently ignored.
9
25
  */
26
+ export declare function toolCallsFromMessages(messages: unknown): ToolCallRow[];
10
27
  export default class AgentsToolsCalls extends BaseCommand {
11
28
  static description: string;
12
29
  static examples: string[];
@@ -16,3 +33,4 @@ export default class AgentsToolsCalls extends BaseCommand {
16
33
  };
17
34
  run(): Promise<void>;
18
35
  }
36
+ export {};
@@ -1,19 +1,58 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.toolCallsFromMessages = toolCallsFromMessages;
3
4
  const core_1 = require("@oclif/core");
4
5
  const data_provider_1 = require("@skrr-ai/data-provider");
5
6
  const base_command_1 = require("../../../base-command");
6
7
  const format_1 = require("../../../lib/format");
8
+ const clip = (value, width) => {
9
+ const text = value == null
10
+ ? ''
11
+ : typeof value === 'string'
12
+ ? value
13
+ : (JSON.stringify(value) ?? String(value));
14
+ const flat = text.replace(/\s+/g, ' ').trim();
15
+ return flat.length > width ? `${flat.slice(0, width - 1)}…` : flat;
16
+ };
7
17
  /**
8
- * Tool calls recorded in one conversation.
18
+ * The tool calls in a conversation, read from the conversation's own messages.
9
19
  *
10
- * `conversationId` is REQUIRED by the endpoint, not optional with a default, so
11
- * it is a required flag rather than something this command guesses — a
12
- * conversation-less listing would silently be somebody else's transcript or
13
- * nothing at all.
20
+ * This command read `GET /api/agents/tools/calls`, the `ToolCall` ledger — and
21
+ * nothing in the codebase writes that ledger: `createToolCall` has no caller. So
22
+ * it answered "No tool calls recorded" for EVERY conversation, including one where
23
+ * the agent had just run a Bash command, and for a conversation id that did not
24
+ * exist at all. Nobody could tell a broken command from an empty answer.
25
+ *
26
+ * Every agent turn records its tool calls as `tool_call` content parts on the
27
+ * assistant message (name, arguments, output), so that is the primary source. A
28
+ * conversation that does not exist is a NOT_FOUND, not an empty list. Ledger rows
29
+ * are still read and merged, so the endpoint stays reachable and a future writer
30
+ * is not silently ignored.
14
31
  */
32
+ function toolCallsFromMessages(messages) {
33
+ const rows = [];
34
+ for (const message of Array.isArray(messages) ? messages : []) {
35
+ const m = message;
36
+ for (const part of Array.isArray(m.content) ? m.content : []) {
37
+ const p = part;
38
+ if (p?.type !== 'tool_call' || !p.tool_call)
39
+ continue;
40
+ const call = p.tool_call;
41
+ rows.push({
42
+ messageId: String(m.messageId ?? '-'),
43
+ toolUseId: String(call.id ?? '-'),
44
+ name: String(call.name ?? '-'),
45
+ args: clip(call.args, 200),
46
+ output: clip(call.output, 200),
47
+ complete: Number(call.progress ?? 0) >= 1 || call.output != null,
48
+ source: 'message',
49
+ });
50
+ }
51
+ }
52
+ return rows;
53
+ }
15
54
  class AgentsToolsCalls extends base_command_1.BaseCommand {
16
- static description = 'List the tool calls recorded in one conversation';
55
+ static description = 'List the tool calls an agent made in one conversation';
17
56
  static examples = [
18
57
  '<%= config.bin %> agents tools calls --conversation <conversation-id>',
19
58
  ];
@@ -24,30 +63,54 @@ class AgentsToolsCalls extends base_command_1.BaseCommand {
24
63
  async run() {
25
64
  this.requireAuth();
26
65
  const { flags } = await this.parse(AgentsToolsCalls);
27
- let calls;
66
+ let messages;
28
67
  try {
29
- calls = await data_provider_1.dataService.getToolCalls({ conversationId: flags.conversation });
68
+ // NOT_FOUND here is the honest answer for a conversation that does not
69
+ // exist, and handleApiError reports it as such.
70
+ messages = await data_provider_1.dataService.getMessagesByConvoId(flags.conversation);
30
71
  }
31
72
  catch (err) {
32
73
  this.handleApiError(err);
33
74
  }
75
+ const rows = toolCallsFromMessages(messages);
76
+ try {
77
+ const ledger = (await data_provider_1.dataService.getToolCalls({
78
+ conversationId: flags.conversation,
79
+ }));
80
+ for (const entry of ledger ?? []) {
81
+ rows.push({
82
+ messageId: String(entry.messageId ?? '-'),
83
+ toolUseId: String(entry.toolId ?? '-'),
84
+ name: String(entry.toolId ?? '-'),
85
+ args: '',
86
+ output: clip(entry.result, 200),
87
+ complete: true,
88
+ source: 'ledger',
89
+ });
90
+ }
91
+ }
92
+ catch {
93
+ /* the ledger is supplementary; the messages already answered */
94
+ }
34
95
  if (flags.json) {
35
- this.log(JSON.stringify(calls, null, 2));
96
+ this.log(JSON.stringify(rows, null, 2));
36
97
  return;
37
98
  }
38
- const rows = (calls ?? []);
39
99
  if (rows.length === 0) {
40
- this.log(`No tool calls recorded in conversation ${flags.conversation}.`);
100
+ const count = Array.isArray(messages) ? messages.length : 0;
101
+ this.log(`Conversation ${flags.conversation} has ${count} message${count === 1 ? '' : 's'} and no tool calls.`);
41
102
  return;
42
103
  }
43
- (0, format_1.renderTable)(rows.map((call) => ({
44
- toolId: String(call.toolId ?? '-'),
45
- messageId: String(call.messageId ?? '-'),
46
- blockIndex: call.blockIndex === undefined ? '-' : String(call.blockIndex),
104
+ (0, format_1.renderTable)(rows.map((row) => ({
105
+ name: row.name,
106
+ args: row.args || '-',
107
+ output: row.complete ? row.output || '(empty)' : '(no result recorded)',
108
+ toolUseId: row.toolUseId,
47
109
  })), [
48
- { key: 'toolId', header: 'TOOL', verbatim: true },
49
- { key: 'messageId', header: 'MESSAGE', verbatim: true },
50
- { key: 'blockIndex', header: 'BLOCK' },
110
+ { key: 'name', header: 'TOOL', maxWidth: 28 },
111
+ { key: 'args', header: 'ARGUMENTS', maxWidth: 48 },
112
+ { key: 'output', header: 'OUTPUT', maxWidth: 40 },
113
+ { key: 'toolUseId', header: 'TOOL USE ID', verbatim: true },
51
114
  ], (line) => this.log(line));
52
115
  }
53
116
  }
@@ -2,6 +2,8 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const core_1 = require("@oclif/core");
4
4
  const data_provider_1 = require("@skrr-ai/data-provider");
5
+ const agent_visual_refs_1 = require("../../lib/agent-visual-refs");
6
+ const create_1 = require("./create");
5
7
  const base_command_1 = require("../../base-command");
6
8
  const execution_target_1 = require("../../lib/execution-target");
7
9
  const prompt_1 = require("../../lib/prompt");
@@ -56,10 +58,7 @@ class AgentsUpdate extends base_command_1.BaseCommand {
56
58
  'network-agent-ids': core_1.Flags.string({ description: 'Network agent IDs JSON array' }),
57
59
  'network-include-public': core_1.Flags.boolean({ description: 'Include public agents in network' }),
58
60
  'character-3d-id': core_1.Flags.string({ description: '3D character ID' }),
59
- 'scene-background': core_1.Flags.string({
60
- description: 'Backdrop for the 3D view: default, preset:<id>, or image:<filepath>@<source>. ' +
61
- 'A bare string is refused — the stored field is a typed reference, not a name.',
62
- }),
61
+ 'scene-background': core_1.Flags.string({ description: agent_visual_refs_1.SCENE_BACKGROUND_FLAG_HELP }),
63
62
  'tone-mode': core_1.Flags.string({ description: 'Tone mode' }),
64
63
  'tone-id': core_1.Flags.string({ description: 'Tone ID' }),
65
64
  'tone-notes': core_1.Flags.string({ description: 'Per-agent tone notes' }),
@@ -117,7 +116,9 @@ class AgentsUpdate extends base_command_1.BaseCommand {
117
116
  allowNo: true,
118
117
  }),
119
118
  'project-id': core_1.Flags.string({
120
- description: 'Prompt-store project this agent belongs to (repeatable)',
119
+ description: 'Prompt-store project to add this agent to (repeatable): a 24-character ObjectId — in ' +
120
+ 'practice the instance-wide project that shares an agent with everyone, which needs the ' +
121
+ 'SHARED_GLOBAL permission. Not a Space project id (`skrr projects`).',
121
122
  multiple: true,
122
123
  }),
123
124
  'remove-project-id': core_1.Flags.string({
@@ -184,6 +185,15 @@ class AgentsUpdate extends base_command_1.BaseCommand {
184
185
  catch (err) {
185
186
  this.handleApiError(err);
186
187
  }
188
+ // The same check `agents create` makes. Without it, a key the server does
189
+ // not model — or a typo of one that it does, `max_turns` for `maxTurns` —
190
+ // was discarded with the same "Updated agent" line as a real change.
191
+ // `removeProjectIds` is an instruction, not a stored field, so it never
192
+ // comes back and is not reported.
193
+ const unapplied = (0, create_1.unappliedPayloadKeys)(payload, updated, ['removeProjectIds']);
194
+ if (unapplied.length > 0) {
195
+ this.warn(`The server did not keep ${unapplied.join(', ')} — ${unapplied.length === 1 ? 'that field is' : 'those fields are'} not part of the agent. Check \`skrr agents show <id> --json\` for the field names it stores.`);
196
+ }
187
197
  if (flags.json) {
188
198
  this.log(JSON.stringify((0, web_url_1.withAgentUrl)(this.cliConfig.baseURL, updated), null, 2));
189
199
  return;
@@ -2,7 +2,7 @@ import { Command } from '@oclif/core';
2
2
  /**
3
3
  * `skrr code skrr-agent` — read and write the agent a managed session runs as.
4
4
  *
5
- * ── Why the name carries the `oversky-` prefix ─────────────────────────────
5
+ * ── Why the name carries a product prefix ─────────────────────────────────
6
6
  *
7
7
  * The engine owns `agent`: the engine binary's `agent` is its own agent-management command.
8
8
  * `skrr code` forwards argv VERBATIM, and oclif's spaced-topic dispatch collates
@@ -28,8 +28,8 @@ import { Command } from '@oclif/core';
28
28
  * API through `apiFetch`, not the `dataService` adapter `BaseCommand.init`
29
29
  * installs.
30
30
  */
31
- export default class CodeOverskyAgent extends Command {
32
- static aliases: string[];
31
+ export default class CodeSkrrAgent extends Command {
32
+ static hiddenAliases: string[];
33
33
  static description: string;
34
34
  static examples: string[];
35
35
  static flags: {
@@ -9,7 +9,7 @@ const first_party_harness_managed_1 = require("../../lib/first-party-harness-man
9
9
  /**
10
10
  * `skrr code skrr-agent` — read and write the agent a managed session runs as.
11
11
  *
12
- * ── Why the name carries the `oversky-` prefix ─────────────────────────────
12
+ * ── Why the name carries a product prefix ─────────────────────────────────
13
13
  *
14
14
  * The engine owns `agent`: the engine binary's `agent` is its own agent-management command.
15
15
  * `skrr code` forwards argv VERBATIM, and oclif's spaced-topic dispatch collates
@@ -35,11 +35,14 @@ const first_party_harness_managed_1 = require("../../lib/first-party-harness-man
35
35
  * API through `apiFetch`, not the `dataService` adapter `BaseCommand.init`
36
36
  * installs.
37
37
  */
38
- class CodeOverskyAgent extends core_1.Command {
39
- // `skrr code skrr-agent` is the spelling to teach; the `oversky-` one is the
40
- // path this file has and stays reachable, because it is in scripts and in
41
- // muscle memory.
42
- static aliases = ['code:skrr-agent'];
38
+ class CodeSkrrAgent extends core_1.Command {
39
+ // `skrr code skrr-agent` is the name (docs/product/skrr-naming-map.md). The
40
+ // file used to be `oversky-agent.ts`, and oclif names a command after its
41
+ // file, so help, `skrr commands` and `skrr usage` taught the retired spelling
42
+ // while every remedy taught this one, and each help page showed the name you
43
+ // typed as its own alias (OSK-9396). The old spelling stays reachable for
44
+ // scripts and muscle memory, and hidden, as the naming map records.
45
+ static hiddenAliases = ['code:oversky-agent'];
43
46
  static description = `Show or set the skrr agent a managed ${auth_core_1.FIRST_PARTY_HARNESS.displayName} session is attributed to`;
44
47
  static examples = [
45
48
  '<%= config.bin %> code skrr-agent',
@@ -68,7 +71,7 @@ class CodeOverskyAgent extends core_1.Command {
68
71
  json: core_1.Flags.boolean({ description: 'Emit the result as JSON' }),
69
72
  };
70
73
  async run() {
71
- const { flags } = await this.parse(CodeOverskyAgent);
74
+ const { flags } = await this.parse(CodeSkrrAgent);
72
75
  // Every branch except `--clear` reads `/api/agents`, and this command extends
73
76
  // `Command` rather than `BaseCommand` — so nothing has resolved the stored
74
77
  // credential for `apiFetch` yet. Without this a signed-in user gets a bare
@@ -184,7 +187,7 @@ class CodeOverskyAgent extends core_1.Command {
184
187
  throw err;
185
188
  }
186
189
  }
187
- exports.default = CodeOverskyAgent;
190
+ exports.default = CodeSkrrAgent;
188
191
  function summarize(agent) {
189
192
  return { id: agent.id, name: agent.name ?? null, source: agent.source };
190
193
  }
@@ -1,4 +1,17 @@
1
1
  import { BaseCommand } from '../../base-command';
2
+ /**
3
+ * The lines `skrr convos messages` prints for one message row.
4
+ *
5
+ * A failed turn used to print as its text and nothing else — usually empty,
6
+ * which reads as an agent that replied with whitespace, while `error: true` and
7
+ * the reason sat on the row being printed. It is now marked, and its reason is
8
+ * printed where the answer would be (OSK-9468). The reason is read through
9
+ * `resolveMessageFailure`, the one reader every surface shares; a legacy error
10
+ * row's text IS that reason, so only the answer is printed as the body.
11
+ */
12
+ export declare function formatMessageLines(m: Record<string, unknown>, options?: {
13
+ metadata?: boolean;
14
+ }): string[];
2
15
  /**
3
16
  * `skrr convos messages` — read a conversation's transcript.
4
17
  *
@@ -1,9 +1,47 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.formatMessageLines = formatMessageLines;
3
4
  const core_1 = require("@oclif/core");
4
5
  const data_provider_1 = require("@skrr-ai/data-provider");
5
6
  const base_command_1 = require("../../base-command");
6
7
  const message_content_1 = require("../../lib/message-content");
8
+ /**
9
+ * The lines `skrr convos messages` prints for one message row.
10
+ *
11
+ * A failed turn used to print as its text and nothing else — usually empty,
12
+ * which reads as an agent that replied with whitespace, while `error: true` and
13
+ * the reason sat on the row being printed. It is now marked, and its reason is
14
+ * printed where the answer would be (OSK-9468). The reason is read through
15
+ * `resolveMessageFailure`, the one reader every surface shares; a legacy error
16
+ * row's text IS that reason, so only the answer is printed as the body.
17
+ */
18
+ function formatMessageLines(m, options = {}) {
19
+ const who = m.isCreatedByUser ? 'user' : String(m.sender ?? 'assistant');
20
+ const when = m.createdAt ? new Date(String(m.createdAt)).toISOString() : '';
21
+ const failure = (0, data_provider_1.resolveMessageFailure)(m);
22
+ // Render each assistant text part as its own block so a tool-paused turn
23
+ // reads as separate paragraphs instead of one glued run-on line
24
+ // (OSK-4911). Whitespace is collapsed WITHIN a block for compactness; the
25
+ // blank line BETWEEN blocks is what the flattened `text` field dropped.
26
+ const body = (0, message_content_1.messageTextBlocks)(failure ? { ...m, text: failure.answer } : m)
27
+ .map((block) => block.replace(/\s+/g, ' ').trim())
28
+ .join('\n\n');
29
+ const clipped = body.length > 300 ? `${body.slice(0, 300)}…` : body;
30
+ const lines = [`${when} ${who}${failure ? ' [failed]' : ''}`];
31
+ if (clipped || !failure) {
32
+ for (const line of clipped.split('\n')) {
33
+ lines.push(` ${line}`);
34
+ }
35
+ }
36
+ if (failure) {
37
+ const reason = failure.reason?.replace(/\s+/g, ' ').trim();
38
+ lines.push(` failed: ${reason || 'no reason was recorded on the message'}`);
39
+ }
40
+ if (options.metadata && m.metadata && Object.keys(m.metadata).length > 0) {
41
+ lines.push(` metadata: ${JSON.stringify(m.metadata)}`);
42
+ }
43
+ return lines;
44
+ }
7
45
  /**
8
46
  * `skrr convos messages` — read a conversation's transcript.
9
47
  *
@@ -70,22 +108,8 @@ class ConvosMessages extends base_command_1.BaseCommand {
70
108
  return;
71
109
  }
72
110
  for (const m of messages) {
73
- const who = m.isCreatedByUser ? 'user' : String(m.sender ?? 'assistant');
74
- const when = m.createdAt ? new Date(String(m.createdAt)).toISOString() : '';
75
- // Render each assistant text part as its own block so a tool-paused turn
76
- // reads as separate paragraphs instead of one glued run-on line
77
- // (OSK-4911). Whitespace is collapsed WITHIN a block for compactness; the
78
- // blank line BETWEEN blocks is what the flattened `text` field dropped.
79
- const body = (0, message_content_1.messageTextBlocks)(m)
80
- .map((block) => block.replace(/\s+/g, ' ').trim())
81
- .join('\n\n');
82
- const clipped = body.length > 300 ? `${body.slice(0, 300)}…` : body;
83
- this.log(`${when} ${who}`);
84
- for (const line of clipped.split('\n')) {
85
- this.log(` ${line}`);
86
- }
87
- if (flags.metadata && m.metadata && Object.keys(m.metadata).length > 0) {
88
- this.log(` metadata: ${JSON.stringify(m.metadata)}`);
111
+ for (const line of formatMessageLines(m, { metadata: flags.metadata })) {
112
+ this.log(line);
89
113
  }
90
114
  }
91
115
  this.log(`\n${messages.length} message(s)`);
@@ -13,7 +13,14 @@ class Daemon extends core_1.Command {
13
13
  // "Wraps the `skrrd` binary" read, cold, as though you must already HAVE one —
14
14
  // the opposite of what `skrr daemon install` does now that it fetches the
15
15
  // signed release. Dogfooding 0.1.12 hit exactly that reading.
16
- static description = 'Manage the local skrr runtime — install it, sign it in, and check on it.';
16
+ // "runtime" names two unrelated things in this product: the background
17
+ // service on the computer you are typing on, and a rented Linux VM (every
18
+ // Dedicated Runtime's daemon id is `dedicated-runtime-rtl_…`). This topic is
19
+ // always the first. A dogfood note put the cost precisely: when `daemon
20
+ // status` warned about an update, the reader had to stop and work out whether
21
+ // it meant their laptop or one of their VMs. Naming the computer removes the
22
+ // hesitation without renaming a domain.
23
+ static description = 'Manage the skrr background service on THIS computer — install it, sign it in, and check on it. A rented Linux VM is a machine, not this: see `skrr machines`.';
17
24
  static examples = [
18
25
  '<%= config.bin %> daemon install',
19
26
  '<%= config.bin %> daemon status',
@@ -24,6 +24,32 @@ export interface DaemonLogsFlags {
24
24
  * constrained to the same profile grammar instead of trusting a lock file as
25
25
  * an arbitrary path component.
26
26
  */
27
+ /**
28
+ * How old the log is, said out loud before any of it is printed.
29
+ *
30
+ * A log file is the one surface whose CONTENT looks equally plausible whether
31
+ * it is live or nine days dead — every line is well-formed and timestamped, and
32
+ * the timestamps are only wrong if you happen to read them. Observed on this
33
+ * machine: a daemon running since 06:20 today, launched with
34
+ * `--log-file ~/Library/Logs/OverSky/daemon.app.log` whose parent directory did
35
+ * not exist, so it wrote nothing; `skrr daemon logs` then served
36
+ * `~/.skrr/profiles/default/logs/daemon.log`, last written nine days earlier,
37
+ * with no marker. The top line read `[2026-09-08 02:55:24]` and nothing said
38
+ * that was not today.
39
+ *
40
+ * The daemon's own logger now creates its directory and reports a failed open
41
+ * (its comment cites OSK-7737/OSK-7699), and `daemon status` learned to say the
42
+ * file predates the running daemon. `daemon logs` — the command you actually
43
+ * reach for — never did.
44
+ *
45
+ * Stated as a FACT first and a warning second: the age is always printed, so a
46
+ * fresh log confirms itself, and no threshold has to be right for the reader to
47
+ * be correctly informed. The warning only adds a next step.
48
+ */
49
+ export declare function describeLogAge(mtimeMs: number, now?: number): {
50
+ line: string;
51
+ stale: boolean;
52
+ };
27
53
  export declare function resolveLegacyDaemonLogPath(flags: Pick<DaemonLogsFlags, 'stderr'>, options?: {
28
54
  home?: string;
29
55
  env?: NodeJS.ProcessEnv;
@@ -3,6 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.describeLogAge = describeLogAge;
6
7
  exports.resolveLegacyDaemonLogPath = resolveLegacyDaemonLogPath;
7
8
  exports.readDaemonLogTail = readDaemonLogTail;
8
9
  exports.buildOverskyLogsArgs = buildOverskyLogsArgs;
@@ -19,6 +20,47 @@ const PROFILE_NAME = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/;
19
20
  * constrained to the same profile grammar instead of trusting a lock file as
20
21
  * an arbitrary path component.
21
22
  */
23
+ /**
24
+ * How old the log is, said out loud before any of it is printed.
25
+ *
26
+ * A log file is the one surface whose CONTENT looks equally plausible whether
27
+ * it is live or nine days dead — every line is well-formed and timestamped, and
28
+ * the timestamps are only wrong if you happen to read them. Observed on this
29
+ * machine: a daemon running since 06:20 today, launched with
30
+ * `--log-file ~/Library/Logs/OverSky/daemon.app.log` whose parent directory did
31
+ * not exist, so it wrote nothing; `skrr daemon logs` then served
32
+ * `~/.skrr/profiles/default/logs/daemon.log`, last written nine days earlier,
33
+ * with no marker. The top line read `[2026-09-08 02:55:24]` and nothing said
34
+ * that was not today.
35
+ *
36
+ * The daemon's own logger now creates its directory and reports a failed open
37
+ * (its comment cites OSK-7737/OSK-7699), and `daemon status` learned to say the
38
+ * file predates the running daemon. `daemon logs` — the command you actually
39
+ * reach for — never did.
40
+ *
41
+ * Stated as a FACT first and a warning second: the age is always printed, so a
42
+ * fresh log confirms itself, and no threshold has to be right for the reader to
43
+ * be correctly informed. The warning only adds a next step.
44
+ */
45
+ function describeLogAge(mtimeMs, now = Date.now()) {
46
+ const ageMs = Math.max(0, now - mtimeMs);
47
+ const minutes = Math.floor(ageMs / 60_000);
48
+ const human = minutes < 1
49
+ ? 'less than a minute ago'
50
+ : minutes < 60
51
+ ? `${minutes} minute${minutes === 1 ? '' : 's'} ago`
52
+ : minutes < 60 * 24
53
+ ? `${Math.floor(minutes / 60)} hour${Math.floor(minutes / 60) === 1 ? '' : 's'} ago`
54
+ : `${Math.floor(minutes / (60 * 24))} day${Math.floor(minutes / (60 * 24)) === 1 ? '' : 's'} ago`;
55
+ const stamp = new Date(mtimeMs).toISOString().slice(0, 19).replace('T', ' ');
56
+ const stale = ageMs > 60 * 60_000;
57
+ return {
58
+ line: stale
59
+ ? `Log last written ${stamp}Z — ${human}. If your daemon is running it is not writing here; check \`skrr daemon status\` for the path it was started with.`
60
+ : `Log last written ${stamp}Z — ${human}.`,
61
+ stale,
62
+ };
63
+ }
22
64
  function resolveLegacyDaemonLogPath(flags, options = {}) {
23
65
  const home = options.home ?? (0, node_os_1.homedir)();
24
66
  const env = options.env ?? process.env;
@@ -118,6 +160,20 @@ class DaemonLogs extends core_1.Command {
118
160
  async run() {
119
161
  const { flags } = await this.parse(DaemonLogs);
120
162
  const args = buildOverskyLogsArgs(flags);
163
+ // Before ANY log content, on stderr so it never pollutes a pipe. `--path`
164
+ // is exempt: its whole contract is one path on stdout.
165
+ if (!flags.path) {
166
+ try {
167
+ const candidate = resolveLegacyDaemonLogPath(flags);
168
+ if ((0, node_fs_1.existsSync)(candidate)) {
169
+ const { line } = describeLogAge((0, node_fs_1.statSync)(candidate).mtimeMs);
170
+ this.logToStderr(line);
171
+ }
172
+ }
173
+ catch {
174
+ // Never let the banner stop the logs from being shown.
175
+ }
176
+ }
121
177
  try {
122
178
  // `oversky 0.2.x` has profiles and writes the same files, but has no
123
179
  // `logs` subcommand. Probe top-level help first so forwarding does not
@@ -19,7 +19,7 @@ class DaemonUninstall extends core_1.Command {
19
19
  // whole topic, and without a per-command alias only the bare topic
20
20
  // resolved: every subcommand under it failed.
21
21
  static aliases = ['daemons:uninstall'];
22
- static summary = "Remove the local skrr runtime's background service (local state is left alone)";
22
+ static summary = 'Remove the skrr background service from THIS computer (local state is left alone; a rented VM is a machine, see `skrr machines`)';
23
23
  static description = (0, exec_runtime_binary_1.forwardedFlagsNote)('uninstall');
24
24
  static strict = false; // forward all args verbatim — see run()
25
25
  async run() {