@skrr-ai/cli 0.1.42 → 0.1.44

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 (120) hide show
  1. package/dist/base-command.d.ts +2 -0
  2. package/dist/base-command.js +1 -0
  3. package/dist/commands/balance/index.js +1 -1
  4. package/dist/commands/balance/overage.js +4 -3
  5. package/dist/commands/balance/plan.js +10 -6
  6. package/dist/commands/balance/show.d.ts +32 -0
  7. package/dist/commands/balance/show.js +74 -3
  8. package/dist/commands/balance/usage/events.js +3 -2
  9. package/dist/commands/balance/usage.js +2 -1
  10. package/dist/commands/browser/install.js +3 -2
  11. package/dist/commands/browser/uninstall.js +3 -2
  12. package/dist/commands/code/handover.d.ts +1 -0
  13. package/dist/commands/code/handover.js +4 -0
  14. package/dist/commands/code/install.js +3 -2
  15. package/dist/commands/code/jobs/run.d.ts +1 -0
  16. package/dist/commands/code/jobs/run.js +4 -0
  17. package/dist/commands/daemon/install.js +7 -0
  18. package/dist/commands/harnesses/install.d.ts +11 -0
  19. package/dist/commands/harnesses/install.js +33 -14
  20. package/dist/commands/harnesses/installers.d.ts +10 -0
  21. package/dist/commands/harnesses/installers.js +31 -0
  22. package/dist/commands/harnesses/leases/show.js +14 -0
  23. package/dist/commands/harnesses/list.js +3 -2
  24. package/dist/commands/instructions/install.d.ts +22 -0
  25. package/dist/commands/instructions/install.js +83 -7
  26. package/dist/commands/instructions/list.js +5 -0
  27. package/dist/commands/instructions/show.d.ts +6 -0
  28. package/dist/commands/instructions/show.js +34 -1
  29. package/dist/commands/instructions/status.js +17 -1
  30. package/dist/commands/payments/wallet.js +2 -2
  31. package/dist/commands/tasks/attachments/set-role.d.ts +24 -0
  32. package/dist/commands/tasks/attachments/set-role.js +60 -0
  33. package/dist/commands/tasks/attachments/upload.d.ts +1 -0
  34. package/dist/commands/tasks/attachments/upload.js +18 -52
  35. package/dist/commands/tasks/comments/add.d.ts +1 -0
  36. package/dist/commands/tasks/comments/add.js +67 -2
  37. package/dist/commands/tasks/complete.d.ts +1 -0
  38. package/dist/commands/tasks/complete.js +56 -1
  39. package/dist/commands/tasks/create.d.ts +6 -0
  40. package/dist/commands/tasks/create.js +26 -3
  41. package/dist/commands/tasks/labels/attach.js +4 -0
  42. package/dist/commands/tasks/list.d.ts +1 -0
  43. package/dist/commands/tasks/list.js +9 -0
  44. package/dist/commands/tasks/result/submit.d.ts +3 -0
  45. package/dist/commands/tasks/result/submit.js +76 -8
  46. package/dist/commands/tasks/show.d.ts +23 -0
  47. package/dist/commands/tasks/show.js +61 -1
  48. package/dist/commands/tasks/update.js +16 -2
  49. package/dist/commands/tasks/updates/add.d.ts +1 -0
  50. package/dist/commands/tasks/updates/add.js +63 -0
  51. package/dist/commands/views/create.js +12 -2
  52. package/dist/commands/views/list.js +3 -2
  53. package/dist/commands/views/show.js +2 -0
  54. package/dist/lib/agentic-stream.d.ts +10 -4
  55. package/dist/lib/agentic-stream.js +25 -11
  56. package/dist/lib/api-fetch.js +7 -2
  57. package/dist/lib/cli-installers.d.ts +23 -4
  58. package/dist/lib/cli-installers.js +56 -8
  59. package/dist/lib/daemon-setup.d.ts +18 -1
  60. package/dist/lib/daemon-setup.js +33 -1
  61. package/dist/lib/dedicated-machines.js +4 -2
  62. package/dist/lib/file-mime.js +1 -1
  63. package/dist/lib/first-party-harness-agent.d.ts +16 -1
  64. package/dist/lib/first-party-harness-agent.js +47 -17
  65. package/dist/lib/first-party-harness-doctor.js +49 -48
  66. package/dist/lib/first-party-harness-managed.d.ts +9 -4
  67. package/dist/lib/first-party-harness-managed.js +13 -10
  68. package/dist/lib/first-party-harness.d.ts +32 -21
  69. package/dist/lib/first-party-harness.js +49 -31
  70. package/dist/lib/harness-provider-input.d.ts +18 -12
  71. package/dist/lib/harness-provider-input.js +18 -12
  72. package/dist/lib/harness-tiers.d.ts +4 -3
  73. package/dist/lib/harness-tiers.js +4 -3
  74. package/dist/lib/harnesses.d.ts +6 -0
  75. package/dist/lib/html-text.js +25 -0
  76. package/dist/lib/instruction-input.d.ts +21 -0
  77. package/dist/lib/instruction-input.js +30 -0
  78. package/dist/lib/instruction-provenance.d.ts +54 -0
  79. package/dist/lib/instruction-provenance.js +84 -0
  80. package/dist/lib/session-task-endpoints.d.ts +1 -1
  81. package/dist/lib/session-task-endpoints.js +3 -0
  82. package/dist/lib/task-asset-upload.d.ts +84 -0
  83. package/dist/lib/task-asset-upload.js +374 -0
  84. package/dist/lib/task-closure.d.ts +15 -0
  85. package/dist/lib/task-closure.js +20 -0
  86. package/dist/lib/task-view-render.d.ts +14 -0
  87. package/dist/lib/task-view-render.js +55 -0
  88. package/dist/lib/tasks.d.ts +26 -0
  89. package/dist/lib/tasks.js +38 -1
  90. package/dist/lib/views/vocabulary.d.ts +1 -1
  91. package/dist/lib/views/vocabulary.js +3 -1
  92. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/firstPartyHarness.d.ts +183 -58
  93. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/firstPartyHarness.js +286 -100
  94. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/firstPartyHarnessChannels.d.ts +21 -33
  95. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/firstPartyHarnessChannels.js +24 -50
  96. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/firstPartyHarnessHome.d.ts +19 -7
  97. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/firstPartyHarnessHome.js +26 -10
  98. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/harnessTrust.d.ts +9 -5
  99. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/harnessTrust.js +9 -5
  100. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.d.ts +1 -1
  101. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.js +1 -13
  102. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/legacyStatePreflight.d.ts +21 -1
  103. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/legacyStatePreflight.js +75 -19
  104. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/firstPartyHarness.d.ts +183 -58
  105. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/firstPartyHarness.js +279 -99
  106. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/firstPartyHarnessChannels.d.ts +21 -33
  107. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/firstPartyHarnessChannels.js +24 -49
  108. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/firstPartyHarnessHome.d.ts +19 -7
  109. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/firstPartyHarnessHome.js +27 -11
  110. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/harnessTrust.d.ts +9 -5
  111. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/harnessTrust.js +9 -5
  112. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.d.ts +1 -1
  113. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.js +1 -4
  114. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/legacyStatePreflight.d.ts +21 -1
  115. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/legacyStatePreflight.js +74 -19
  116. package/dist/node_modules/@skrr-ai/auth-core/package.json +1 -1
  117. package/dist/node_modules/@skrr-ai/data-provider/index.js +4588 -4509
  118. package/dist/node_modules/@skrr-ai/data-provider/package.json +1 -1
  119. package/oclif.manifest.json +33980 -33832
  120. package/package.json +3 -3
@@ -1,5 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.conflictingTargetOf = conflictingTargetOf;
4
+ exports.describeTargetConflict = describeTargetConflict;
3
5
  const core_1 = require("@oclif/core");
4
6
  const auth_core_1 = require("@skrr-ai/auth-core");
5
7
  const data_provider_1 = require("@skrr-ai/data-provider");
@@ -9,6 +11,41 @@ const harness_provider_input_1 = require("../../lib/harness-provider-input");
9
11
  const prompt_1 = require("../../lib/prompt");
10
12
  /** The providers a managed instruction can target, canonical spellings. */
11
13
  const MANAGED_INSTRUCTION_PROVIDERS = ['claude', 'codex', auth_core_1.FIRST_PARTY_HARNESS.provider];
14
+ function conflictingTargetOf(body) {
15
+ const value = body?.conflictingTarget;
16
+ return value && typeof value.id === 'string' && value.id ? value : null;
17
+ }
18
+ /**
19
+ * What to do about a placement that already has a target, as commands.
20
+ *
21
+ * Re-running an install whose first attempt had failed printed "An unexpected
22
+ * error occurred. Code: TARGET_CONFLICT" (OSK-4631): no target, no path, no next
23
+ * step, and no word on whether the re-run had partly applied. The API now names
24
+ * the target in the way; this turns that into the commands for its state. A live
25
+ * target is changed or removed, never installed twice; one being removed can be
26
+ * restored, or purged once its daemon confirms cleanup.
27
+ */
28
+ function describeTargetConflict(target, { bin, placement, created }) {
29
+ const state = [target.desiredState, target.status].filter(Boolean).join(', ');
30
+ const lines = target.desiredState === 'removed'
31
+ ? [
32
+ `Target ${target.id} already holds ${placement} on that machine and is being removed${state ? ` (${state})` : ''}.`,
33
+ `Bring it back with \`${bin} instructions restore-target ${target.id}\`, or install again ` +
34
+ `after its daemon confirms cleanup and \`${bin} instructions purge-target ${target.id}\`.`,
35
+ ]
36
+ : [
37
+ `Target ${target.id} already manages ${placement} on that machine${state ? ` (${state})` : ''}.`,
38
+ ...(target.status === 'error'
39
+ ? [`Its last sync failed; \`${bin} instructions status\` shows why.`]
40
+ : []),
41
+ `Change what it applies with \`${bin} instructions update-target ${target.id} --bundle <id>\`, ` +
42
+ `or drop it with \`${bin} instructions remove ${target.id}\`.`,
43
+ ];
44
+ lines.push(created.length
45
+ ? `Created before this refusal: ${created.join(', ')}.`
46
+ : 'No target was created by this command.');
47
+ return lines.join(' ');
48
+ }
12
49
  class InstructionsInstall extends base_command_1.BaseCommand {
13
50
  static description = 'Opt in a daemon target to ongoing managed instruction reconciliation';
14
51
  static flags = {
@@ -32,7 +69,13 @@ class InstructionsInstall extends base_command_1.BaseCommand {
32
69
  default: MANAGED_INSTRUCTION_PROVIDERS,
33
70
  }),
34
71
  scope: core_1.Flags.string({ description: 'global or project', options: ['global', 'project'] }),
35
- project: core_1.Flags.string({ description: 'Absolute project path on the daemon machine' }),
72
+ project: core_1.Flags.string({
73
+ // The daemon writes a project target at the repository root, so a path
74
+ // outside any Git repository is accepted here and then fails on the
75
+ // machine. Saying so is the only warning this command can give: the path
76
+ // is checked where it lives, not here (OSK-4626).
77
+ description: "Absolute project path on the daemon's machine, inside a Git repository (the block is written at its root)",
78
+ }),
36
79
  mode: core_1.Flags.string({
37
80
  description: 'Project footprint',
38
81
  options: ['local', 'team'],
@@ -87,12 +130,20 @@ class InstructionsInstall extends base_command_1.BaseCommand {
87
130
  if (providers.includes('codex') && Buffer.byteLength(previewBody, 'utf8') > 30 * 1024) {
88
131
  this.error('Codex managed instruction bodies are limited to 30 KiB; no targets were created', { exit: 2 });
89
132
  }
90
- if (!flags.json) {
133
+ // The body is shown so the confirmation below is informed. With `--yes`
134
+ // there is no confirmation to inform, and printing a ~9 KB body anyway put
135
+ // the command's actual outcome — often a refusal — a screen of scrollback
136
+ // away (OSK-4631). Say what was authorized, and where to read it.
137
+ if (!flags.json && !flags.yes) {
91
138
  this.log('Exact managed instruction body:');
92
139
  this.log('---');
93
140
  this.log(previewBody);
94
141
  this.log('---');
95
142
  }
143
+ else if (!flags.json) {
144
+ this.log(`Authorizing ${Buffer.byteLength(previewBody, 'utf8').toLocaleString()} bytes of instruction ` +
145
+ `${flags.bundle}; \`${this.config.bin} instructions show ${flags.bundle}\` prints them.`);
146
+ }
96
147
  const teamWarning = scope === 'project' && flags.mode === 'team'
97
148
  ? ' Team mode writes to repository files and may affect every contributor.'
98
149
  : '';
@@ -101,8 +152,8 @@ class InstructionsInstall extends base_command_1.BaseCommand {
101
152
  return this.log('Aborted.');
102
153
  const daemonId = await this.resolveDaemon(flags.daemon);
103
154
  const results = [];
104
- try {
105
- for (const provider of providers) {
155
+ for (const provider of providers) {
156
+ try {
106
157
  results.push(await data_provider_1.dataService.createManagedInstructionTarget({
107
158
  daemonId,
108
159
  bundleId: flags.bundle,
@@ -115,15 +166,40 @@ class InstructionsInstall extends base_command_1.BaseCommand {
115
166
  label: flags.label,
116
167
  }));
117
168
  }
118
- }
119
- catch (error) {
120
- this.handleApiError(error);
169
+ catch (error) {
170
+ // One target per provider, created in turn, so a refusal part-way
171
+ // through leaves the earlier ones in place. Say which.
172
+ const created = results.map((result) => `${(0, harness_provider_input_1.canonicalHarnessInput)(result.target.provider)} (${result.target.id})`);
173
+ const refusal = (0, base_command_1.apiRefusal)(error);
174
+ const conflict = refusal.code === 'TARGET_CONFLICT' ? conflictingTargetOf(refusal.body) : null;
175
+ if (conflict) {
176
+ this.failWithCliError({
177
+ message: describeTargetConflict(conflict, {
178
+ bin: this.config.bin,
179
+ placement: `${(0, harness_provider_input_1.canonicalHarnessInput)(provider)} ${scope}${flags.project ? ` ${flags.project}` : ''}`,
180
+ created,
181
+ }),
182
+ code: 'TARGET_CONFLICT',
183
+ status: 409,
184
+ exit: 1,
185
+ retryable: false,
186
+ details: { ...refusal.body, created: results.map((result) => result.target) },
187
+ });
188
+ }
189
+ if (created.length) {
190
+ this.warn(`Created before this failure: ${created.join(', ')}.`);
191
+ }
192
+ this.handleApiError(error);
193
+ }
121
194
  }
122
195
  if (flags.json)
123
196
  return this.log(JSON.stringify(results, null, 2));
124
197
  for (const result of results) {
125
198
  this.log(`Install queued: ${(0, harness_provider_input_1.canonicalHarnessInput)(result.target.provider)} ${result.target.scope}${result.target.projectPath ? ` ${result.target.projectPath}` : ''} (${result.target.id})`);
126
199
  }
200
+ // Queued is not applied: the daemon checks the placement when it writes, and
201
+ // a path it cannot use fails there, after this command has exited 0.
202
+ this.log(`The daemon applies these on its next sync; \`${this.config.bin} instructions status\` shows when, or why not.`);
127
203
  }
128
204
  /**
129
205
  * The daemon the targets are created on, by the rule every daemon-picking
@@ -4,6 +4,7 @@ const core_1 = require("@oclif/core");
4
4
  const data_provider_1 = require("@skrr-ai/data-provider");
5
5
  const base_command_1 = require("../../base-command");
6
6
  const format_1 = require("../../lib/format");
7
+ const instruction_provenance_1 = require("../../lib/instruction-provenance");
7
8
  class InstructionsList extends base_command_1.BaseCommand {
8
9
  static description = 'List version-controlled instruction prompts';
9
10
  static flags = {
@@ -32,12 +33,16 @@ class InstructionsList extends base_command_1.BaseCommand {
32
33
  id: bundle.id,
33
34
  name: bundle.name,
34
35
  version: String(bundle.currentVersionNumber),
36
+ // The built-in release the CURRENT body is, if any — a separate counter
37
+ // from VERSION, which is this prompt's own (OSK-4647).
38
+ template: (0, instruction_provenance_1.builtInLabel)(bundle.currentVersion),
35
39
  status: bundle.deletedAt ? 'deleted' : bundle.status,
36
40
  updated: bundle.updatedAt,
37
41
  })), [
38
42
  { key: 'id', header: 'ID' },
39
43
  { key: 'name', header: 'NAME', maxWidth: 32 },
40
44
  { key: 'version', header: 'VERSION' },
45
+ { key: 'template', header: 'TEMPLATE' },
41
46
  { key: 'status', header: 'STATUS' },
42
47
  { key: 'updated', header: 'UPDATED' },
43
48
  ], (line) => this.log(line));
@@ -9,4 +9,10 @@ export default class InstructionsShow extends BaseCommand {
9
9
  versions: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
10
10
  };
11
11
  run(): Promise<void>;
12
+ /**
13
+ * The built-in release this server would install today, or undefined. Read
14
+ * from the offer, which already reports it as `templateVersion`; a failure
15
+ * costs only the "is there a newer one" clause, never the command.
16
+ */
17
+ private latestTemplateVersion;
12
18
  }
@@ -4,6 +4,7 @@ const core_1 = require("@oclif/core");
4
4
  const data_provider_1 = require("@skrr-ai/data-provider");
5
5
  const base_command_1 = require("../../base-command");
6
6
  const format_1 = require("../../lib/format");
7
+ const instruction_provenance_1 = require("../../lib/instruction-provenance");
7
8
  class InstructionsShow extends base_command_1.BaseCommand {
8
9
  static description = 'Show exact prompt content and version history';
9
10
  static args = {
@@ -38,6 +39,9 @@ class InstructionsShow extends base_command_1.BaseCommand {
38
39
  (0, format_1.renderTable)(response.versions.map((version) => ({
39
40
  id: version.id,
40
41
  version: String(version.versionNumber),
42
+ // Its own column, never folded into VERSION: the prompt's counter
43
+ // and the built-in release are different numbers (OSK-4647).
44
+ template: (0, instruction_provenance_1.builtInLabel)(version),
41
45
  hash: version.hash.slice(0, 12),
42
46
  status: version.archivedAt
43
47
  ? 'archived'
@@ -48,10 +52,20 @@ class InstructionsShow extends base_command_1.BaseCommand {
48
52
  })), [
49
53
  { key: 'id', header: 'ID' },
50
54
  { key: 'version', header: 'VERSION' },
51
- { key: 'hash', header: 'HASH' },
55
+ { key: 'hash', header: 'BODY HASH' },
52
56
  { key: 'status', header: 'STATUS' },
57
+ { key: 'template', header: 'TEMPLATE' },
53
58
  { key: 'message', header: 'MESSAGE', maxWidth: 48 },
54
59
  ], (line) => this.log(line));
60
+ // Three hashes answer "is my file current?", and this table shows one.
61
+ // A file's `v=` is the body as rendered for its target, so beside an
62
+ // agent-identity section it matches none of these (OSK-4644).
63
+ this.log('');
64
+ this.log("BODY HASH is the version as written. A managed file's `v=` is the body as");
65
+ this.log('rendered for its target, which differs when it fills in an agent identity;');
66
+ this.log(`compare it with FILE HASH in \`${this.config.bin} instructions status\`.`);
67
+ this.log("VERSION counts this prompt's saved bodies; TEMPLATE is the built-in release a");
68
+ this.log('body was taken from, which is a separate number.');
55
69
  return;
56
70
  }
57
71
  const response = await data_provider_1.dataService.getManagedInstructionBundle(args.id);
@@ -61,6 +75,9 @@ class InstructionsShow extends base_command_1.BaseCommand {
61
75
  this.log(`${bundle.name} (${bundle.id}) — v${bundle.currentVersionNumber} — ${bundle.status}`);
62
76
  if (bundle.description)
63
77
  this.log(bundle.description);
78
+ const provenance = (0, instruction_provenance_1.describeTemplateProvenance)(bundle, await this.latestTemplateVersion(bundle.sourceTemplateKey));
79
+ if (provenance)
80
+ this.log(provenance);
64
81
  this.log('');
65
82
  this.log(bundle.currentVersion?.body || '(no active content)');
66
83
  }
@@ -84,5 +101,21 @@ class InstructionsShow extends base_command_1.BaseCommand {
84
101
  this.handleApiError(error);
85
102
  }
86
103
  }
104
+ /**
105
+ * The built-in release this server would install today, or undefined. Read
106
+ * from the offer, which already reports it as `templateVersion`; a failure
107
+ * costs only the "is there a newer one" clause, never the command.
108
+ */
109
+ async latestTemplateVersion(templateKey) {
110
+ if (!templateKey)
111
+ return undefined;
112
+ try {
113
+ const offer = await data_provider_1.dataService.getManagedInstructionOffer(templateKey);
114
+ return offer?.preset?.templateVersion;
115
+ }
116
+ catch {
117
+ return undefined;
118
+ }
119
+ }
87
120
  }
88
121
  exports.default = InstructionsShow;
@@ -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 format_1 = require("../../lib/format");
7
7
  const instruction_input_1 = require("../../lib/instruction-input");
8
+ const instruction_provenance_1 = require("../../lib/instruction-provenance");
8
9
  class InstructionsStatus extends base_command_1.BaseCommand {
9
10
  static description = 'Show managed targets, applied versions, paths, and reconciliation errors';
10
11
  static flags = {
@@ -42,12 +43,14 @@ class InstructionsStatus extends base_command_1.BaseCommand {
42
43
  // Leave it empty.
43
44
  }
44
45
  let bundleNames = new Map();
46
+ let bundlesById = new Map();
45
47
  try {
46
48
  const bundleResponse = await data_provider_1.dataService.listManagedInstructionBundles({
47
49
  includeArchived: true,
48
50
  includeDeleted: true,
49
51
  });
50
52
  bundleNames = (0, instruction_input_1.buildBundleNameIndex)(bundleResponse.bundles);
53
+ bundlesById = new Map(bundleResponse.bundles.map((bundle) => [bundle.id, bundle]));
51
54
  }
52
55
  catch {
53
56
  // Leave the index empty — rows show the full bundle id instead.
@@ -73,7 +76,17 @@ class InstructionsStatus extends base_command_1.BaseCommand {
73
76
  target: target.appliedPath ||
74
77
  (target.scope === 'global' ? 'global' : target.projectPath || 'project'),
75
78
  desired: target.desiredState,
76
- applied: target.appliedVersionNumber ? `v${target.appliedVersionNumber}` : '-',
79
+ // `v3 (built-in v5)`: the prompt's version, and the built-in release
80
+ // it is when that is known. `APPLIED v3` alone read as two releases
81
+ // behind a preset announced as v5 (OSK-4647).
82
+ applied: (0, instruction_provenance_1.appliedVersionLabel)(target.appliedVersionNumber, bundlesById.get(target.bundleId)),
83
+ // The hash the managed block in the file starts with (`v=`), cut to the
84
+ // same 12 characters `show --versions` prints. It is the body as
85
+ // rendered for THIS target, so it matches a version's hash only when
86
+ // nothing was filled in — and was `--json` only, which made "is the
87
+ // block in my CLAUDE.md current?" read as "my machine is running a body
88
+ // the server never heard of" (OSK-4644).
89
+ fileHash: target.appliedHash ? target.appliedHash.slice(0, 12) : '-',
77
90
  status: (0, instruction_input_1.describeTargetSync)(target, Date.now(), liveDaemonIds),
78
91
  })), [
79
92
  { key: 'id', header: 'TARGET ID' },
@@ -88,6 +101,7 @@ class InstructionsStatus extends base_command_1.BaseCommand {
88
101
  { key: 'target', header: 'PATH', maxWidth: 36 },
89
102
  { key: 'desired', header: 'DESIRED' },
90
103
  { key: 'applied', header: 'APPLIED' },
104
+ { key: 'fileHash', header: 'FILE HASH' },
91
105
  // Wide enough for the stall note to keep its remedy. Truncating at 48
92
106
  // cut `purge-target` off the end, which hid the one actionable half of
93
107
  // the sentence.
@@ -106,6 +120,8 @@ class InstructionsStatus extends base_command_1.BaseCommand {
106
120
  for (const command of rebinds)
107
121
  this.log(` ${command}`);
108
122
  }
123
+ for (const line of (0, instruction_input_1.failedTargetLines)(response.targets, this.config.bin))
124
+ this.log(line);
109
125
  // Where the text went.
110
126
  //
111
127
  // The count in the STATUS column says skrr overwrote something you
@@ -52,8 +52,8 @@ class PaymentsWallet extends base_command_1.BaseCommand {
52
52
  // CLI — so anyone looking for "my balance" found USDC and drew the wrong
53
53
  // conclusion about their usage ceiling. Name the other one.
54
54
  this.log('');
55
- this.log('This is the x402 crypto wallet, in USDC. Your hosted USAGE balance —');
56
- this.log('the ceiling that refuses a turn — is `skrr balance show`.');
55
+ this.log('This is the x402 crypto wallet, in USDC. Your hosted USAGE balance and');
56
+ this.log('plan limits — the ceilings that refuse a turn — are `skrr balance show`.');
57
57
  const payments = history.payments ?? [];
58
58
  if (payments.length === 0) {
59
59
  this.log('');
@@ -0,0 +1,24 @@
1
+ import { BaseCommand } from '../../../base-command';
2
+ /**
3
+ * `skrr tasks attachments set-role <task> <attachment> --role …`
4
+ *
5
+ * The one way to change what an existing asset IS on a Task. Its real use is
6
+ * promotion: an agent may only upload `evidence` or `deliverable`, because an
7
+ * `input` is forwarded into every future Run of the Task — so when an agent's
8
+ * screenshot should become an input, a person promotes it here. The server
9
+ * refuses the call from an agent, whatever credential it holds, and moves the
10
+ * asset's ledger link with the role.
11
+ */
12
+ export default class TasksAttachmentsSetRole extends BaseCommand {
13
+ static description: string;
14
+ static examples: string[];
15
+ static args: {
16
+ id: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
17
+ attachmentId: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
18
+ };
19
+ static flags: {
20
+ role: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
21
+ json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
22
+ };
23
+ run(): Promise<void>;
24
+ }
@@ -0,0 +1,60 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const core_1 = require("@oclif/core");
4
+ const data_provider_1 = require("@skrr-ai/data-provider");
5
+ const base_command_1 = require("../../../base-command");
6
+ /**
7
+ * `skrr tasks attachments set-role <task> <attachment> --role …`
8
+ *
9
+ * The one way to change what an existing asset IS on a Task. Its real use is
10
+ * promotion: an agent may only upload `evidence` or `deliverable`, because an
11
+ * `input` is forwarded into every future Run of the Task — so when an agent's
12
+ * screenshot should become an input, a person promotes it here. The server
13
+ * refuses the call from an agent, whatever credential it holds, and moves the
14
+ * asset's ledger link with the role.
15
+ */
16
+ class TasksAttachmentsSetRole extends base_command_1.BaseCommand {
17
+ static description = "Change an attachment's role (input, evidence, or deliverable). Making an asset an input " +
18
+ 'sends it into every future Run of the task, so only a person can change a role.';
19
+ static examples = [
20
+ '<%= config.bin %> tasks attachments set-role <task-id> <attachment-id> --role input',
21
+ '<%= config.bin %> tasks attachments set-role <task-id> <attachment-id> --role evidence --json',
22
+ ];
23
+ static args = {
24
+ id: core_1.Args.string({ description: 'Task ID', required: true, ignoreStdin: true }),
25
+ attachmentId: core_1.Args.string({ description: 'Attachment ID', required: true, ignoreStdin: true }),
26
+ };
27
+ static flags = {
28
+ role: core_1.Flags.string({
29
+ required: true,
30
+ options: ['input', 'evidence', 'deliverable'],
31
+ description: 'input (forwarded into every future Run), evidence (supports an Update or Result), or deliverable (the media is the product)',
32
+ }),
33
+ json: core_1.Flags.boolean({ description: 'Output as JSON' }),
34
+ };
35
+ async run() {
36
+ this.requireAuth();
37
+ const { args, flags } = await this.parse(TasksAttachmentsSetRole);
38
+ let attachment;
39
+ try {
40
+ attachment = await data_provider_1.dataService.setTaskAttachmentRole({
41
+ taskId: args.id,
42
+ attachmentId: args.attachmentId,
43
+ role: flags.role,
44
+ });
45
+ }
46
+ catch (err) {
47
+ this.handleApiError(err);
48
+ }
49
+ if (flags.json) {
50
+ this.log(JSON.stringify(attachment, null, 2));
51
+ return;
52
+ }
53
+ const name = attachment.originalName || attachment.id;
54
+ const effect = attachment.role === 'input'
55
+ ? ' It will be sent into every future Run of this task.'
56
+ : ' It is no longer sent into Runs.';
57
+ this.log(`${name} is now ${attachment.role}.${effect}`);
58
+ }
59
+ }
60
+ exports.default = TasksAttachmentsSetRole;
@@ -8,6 +8,7 @@ export default class TasksAttachmentsUpload extends BaseCommand {
8
8
  static flags: {
9
9
  json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
10
10
  file: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
11
+ role: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
11
12
  };
12
13
  run(): Promise<void>;
13
14
  }
@@ -1,50 +1,14 @@
1
1
  "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || (function () {
19
- var ownKeys = function(o) {
20
- ownKeys = Object.getOwnPropertyNames || function (o) {
21
- var ar = [];
22
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
- return ar;
24
- };
25
- return ownKeys(o);
26
- };
27
- return function (mod) {
28
- if (mod && mod.__esModule) return mod;
29
- var result = {};
30
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
- __setModuleDefault(result, mod);
32
- return result;
33
- };
34
- })();
35
2
  Object.defineProperty(exports, "__esModule", { value: true });
36
- const fs = __importStar(require("node:fs"));
37
- const path = __importStar(require("node:path"));
38
- const node_buffer_1 = require("node:buffer");
39
3
  const core_1 = require("@oclif/core");
40
- const data_provider_1 = require("@skrr-ai/data-provider");
41
4
  const base_command_1 = require("../../../base-command");
42
- const file_mime_1 = require("../../../lib/file-mime");
5
+ const task_asset_upload_1 = require("../../../lib/task-asset-upload");
43
6
  class TasksAttachmentsUpload extends base_command_1.BaseCommand {
44
7
  static description = 'Upload a file attachment to a task';
45
8
  static examples = [
46
9
  '<%= config.bin %> tasks attachments upload <task-id> --file ./report.pdf',
47
10
  '<%= config.bin %> tasks attachments upload <task-id> --file ./screenshot.png --json',
11
+ '<%= config.bin %> tasks attachments upload <task-id> --file ./demo.mp4 --role deliverable',
48
12
  ];
49
13
  static args = {
50
14
  id: core_1.Args.string({ description: 'Task ID', required: true, ignoreStdin: true }),
@@ -52,32 +16,34 @@ class TasksAttachmentsUpload extends base_command_1.BaseCommand {
52
16
  static flags = {
53
17
  json: core_1.Flags.boolean({ description: 'Output as JSON' }),
54
18
  file: core_1.Flags.string({ description: 'Path to the file to upload', required: true }),
19
+ role: core_1.Flags.string({
20
+ description: 'Asset role: evidence (supports an Update/Result), deliverable (the media is the product), or input (forwarded into every future Run — only a person may add one; a person promotes an agent upload with `attachments set-role`). Defaults to evidence when an agent uploads, input when a person does.',
21
+ options: ['input', 'evidence', 'deliverable'],
22
+ }),
55
23
  };
56
24
  async run() {
57
25
  this.requireAuth();
58
26
  const { args, flags } = await this.parse(TasksAttachmentsUpload);
59
- const filePath = flags.file;
60
- if (!fs.existsSync(filePath)) {
61
- this.error(`File not found: ${filePath}`, { exit: 1 });
62
- }
63
- const buf = fs.readFileSync(filePath);
64
- const filename = path.basename(filePath);
65
- const fd = new FormData();
66
- const mimeType = (0, file_mime_1.mimeTypeForFilename)(filename);
67
- fd.append('file', new node_buffer_1.Blob([buf], { type: mimeType }), filename);
68
- let result;
27
+ // The pipeline — buffered vs direct, session vs generic — lives in
28
+ // task-asset-upload.ts so `--attach` on updates/result/complete/comments
29
+ // makes the same routing decisions byte for byte (OSK-9863).
30
+ let asset;
69
31
  try {
70
- result = await data_provider_1.dataService.uploadTaskAttachment({ taskId: args.id, formData: fd });
32
+ asset = await (0, task_asset_upload_1.uploadTaskAssetFile)({
33
+ taskId: args.id,
34
+ filePath: flags.file,
35
+ role: flags.role,
36
+ });
71
37
  }
72
38
  catch (err) {
73
39
  this.handleApiError(err);
74
40
  }
75
41
  if (flags.json) {
76
- this.log(JSON.stringify(result, null, 2));
42
+ this.log(JSON.stringify(asset, null, 2));
77
43
  return;
78
44
  }
79
- const a = result;
80
- this.log(`Uploaded ${filename} (${buf.byteLength} bytes) → attachment ${a.id ?? a._id ?? '?'}`);
45
+ const dedup = asset.deduplicated ? ' (deduplicated — already on the task)' : '';
46
+ this.log(`Uploaded ${asset.filename} → attachment ${asset.id}${dedup}`);
81
47
  }
82
48
  }
83
49
  exports.default = TasksAttachmentsUpload;
@@ -16,6 +16,7 @@ export default class TasksCommentsAdd extends BaseCommand {
16
16
  'parent-comment': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
17
17
  'anchor-mark-id': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
18
18
  'anchor-quote': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
19
+ attach: import("@oclif/core/lib/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
19
20
  };
20
21
  run(): Promise<void>;
21
22
  /**
@@ -1,4 +1,37 @@
1
1
  "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
2
35
  Object.defineProperty(exports, "__esModule", { value: true });
3
36
  const node_crypto_1 = require("node:crypto");
4
37
  const core_1 = require("@oclif/core");
@@ -55,6 +88,11 @@ class TasksCommentsAdd extends base_command_1.BaseCommand {
55
88
  'anchor-quote': core_1.Flags.string({
56
89
  description: 'Captured quote for the anchor (≤1000 chars; truncated to 500 to match the editor). Enables inline-comment mode when used without --anchor-mark-id.',
57
90
  }),
91
+ attach: core_1.Flags.string({
92
+ multiple: true,
93
+ description: 'Upload a file as a Task asset and reference it in the comment body ' +
94
+ '(`![name](task-asset:<id>)`, resolved to a signed URL at read time). Repeatable.',
95
+ }),
58
96
  };
59
97
  async run() {
60
98
  this.requireAuth();
@@ -107,9 +145,36 @@ class TasksCommentsAdd extends base_command_1.BaseCommand {
107
145
  // mentionedAgentIds, reopen, and interrupt are preserved. Boolean flags
108
146
  // default to false; only pass them through when actually set so they
109
147
  // don't clobber a `true` value from the JSON file.
148
+ // --attach: upload first, then append `task-asset:` references to the
149
+ // body — comments store stable asset ids, not URLs (OSK-9856/9863).
150
+ let attachSuffix = '';
151
+ if (flags.attach?.length) {
152
+ const { uploadTaskAssetFile, taskAssetMarkdownRef } = await Promise.resolve().then(() => __importStar(require('../../../lib/task-asset-upload')));
153
+ const refs = [];
154
+ for (const filePath of flags.attach) {
155
+ try {
156
+ const asset = await uploadTaskAssetFile({
157
+ taskId: task.id,
158
+ filePath,
159
+ role: 'evidence',
160
+ });
161
+ refs.push(taskAssetMarkdownRef(asset));
162
+ }
163
+ catch (err) {
164
+ this.error(`--attach failed: ${err.message}`, { exit: 1 });
165
+ }
166
+ }
167
+ if (refs.length)
168
+ attachSuffix = `\n\n${refs.join('\n')}`;
169
+ }
110
170
  const mentionedFromFlag = flags['mention-agent'];
171
+ const baseBody = flags.body ?? (typeof commentBody.body === 'string' ? commentBody.body : undefined);
111
172
  const data = (0, prompt_1.mergeDefined)(commentBody, {
112
- body: flags.body,
173
+ body: baseBody
174
+ ? `${baseBody}${attachSuffix}`
175
+ : attachSuffix
176
+ ? attachSuffix.trimStart()
177
+ : undefined,
113
178
  mentionedAgentIds: Array.isArray(mentionedFromFlag) && mentionedFromFlag.length > 0
114
179
  ? mentionedFromFlag
115
180
  : undefined,
@@ -163,7 +228,7 @@ class TasksCommentsAdd extends base_command_1.BaseCommand {
163
228
  ? await (0, api_fetch_1.apiFetch)(sessionPath, {
164
229
  method: 'POST',
165
230
  body: {
166
- body: flags.body ?? commentBody.body,
231
+ body: data.body,
167
232
  ...(mentioned ? { mentionedAgentIds: mentioned } : {}),
168
233
  },
169
234
  headers: { 'X-Idempotency-Key': idempotencyKey },
@@ -26,6 +26,7 @@ export default class TasksComplete extends BaseCommand {
26
26
  'structured-json': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
27
27
  deliverable: import("@oclif/core/lib/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
28
28
  evidence: import("@oclif/core/lib/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
29
+ attach: import("@oclif/core/lib/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
29
30
  remaining: import("@oclif/core/lib/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
30
31
  'source-run': import("@oclif/core/lib/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
31
32
  'run-id': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;