@skrr-ai/cli 0.1.42 → 0.1.43

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 (74) hide show
  1. package/dist/commands/balance/overage.js +4 -3
  2. package/dist/commands/balance/plan.js +10 -6
  3. package/dist/commands/balance/usage/events.js +3 -2
  4. package/dist/commands/balance/usage.js +2 -1
  5. package/dist/commands/browser/install.js +3 -2
  6. package/dist/commands/browser/uninstall.js +3 -2
  7. package/dist/commands/code/install.js +3 -2
  8. package/dist/commands/harnesses/install.d.ts +11 -0
  9. package/dist/commands/harnesses/install.js +33 -14
  10. package/dist/commands/harnesses/installers.d.ts +10 -0
  11. package/dist/commands/harnesses/installers.js +31 -0
  12. package/dist/commands/harnesses/list.js +3 -2
  13. package/dist/commands/tasks/attachments/set-role.d.ts +24 -0
  14. package/dist/commands/tasks/attachments/set-role.js +60 -0
  15. package/dist/commands/tasks/attachments/upload.d.ts +1 -0
  16. package/dist/commands/tasks/attachments/upload.js +18 -52
  17. package/dist/commands/tasks/comments/add.d.ts +1 -0
  18. package/dist/commands/tasks/comments/add.js +67 -2
  19. package/dist/commands/tasks/complete.d.ts +1 -0
  20. package/dist/commands/tasks/complete.js +56 -1
  21. package/dist/commands/tasks/result/submit.d.ts +3 -0
  22. package/dist/commands/tasks/result/submit.js +76 -8
  23. package/dist/commands/tasks/update.js +4 -2
  24. package/dist/commands/tasks/updates/add.d.ts +1 -0
  25. package/dist/commands/tasks/updates/add.js +63 -0
  26. package/dist/lib/api-fetch.js +7 -2
  27. package/dist/lib/cli-installers.d.ts +23 -4
  28. package/dist/lib/cli-installers.js +47 -7
  29. package/dist/lib/dedicated-machines.js +4 -2
  30. package/dist/lib/file-mime.js +1 -1
  31. package/dist/lib/first-party-harness-agent.js +6 -5
  32. package/dist/lib/first-party-harness-doctor.js +15 -32
  33. package/dist/lib/first-party-harness-managed.d.ts +9 -4
  34. package/dist/lib/first-party-harness-managed.js +13 -10
  35. package/dist/lib/first-party-harness.d.ts +25 -21
  36. package/dist/lib/first-party-harness.js +40 -27
  37. package/dist/lib/harness-provider-input.d.ts +18 -12
  38. package/dist/lib/harness-provider-input.js +18 -12
  39. package/dist/lib/harness-tiers.d.ts +4 -3
  40. package/dist/lib/harness-tiers.js +4 -3
  41. package/dist/lib/html-text.js +25 -0
  42. package/dist/lib/session-task-endpoints.d.ts +1 -1
  43. package/dist/lib/session-task-endpoints.js +3 -0
  44. package/dist/lib/task-asset-upload.d.ts +84 -0
  45. package/dist/lib/task-asset-upload.js +374 -0
  46. package/dist/lib/task-closure.d.ts +15 -0
  47. package/dist/lib/task-closure.js +20 -0
  48. package/dist/lib/tasks.d.ts +8 -0
  49. package/dist/lib/tasks.js +16 -0
  50. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/firstPartyHarness.d.ts +113 -39
  51. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/firstPartyHarness.js +148 -71
  52. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/firstPartyHarnessChannels.d.ts +21 -33
  53. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/firstPartyHarnessChannels.js +24 -50
  54. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/firstPartyHarnessHome.d.ts +19 -7
  55. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/firstPartyHarnessHome.js +26 -10
  56. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/harnessTrust.d.ts +9 -5
  57. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/harnessTrust.js +9 -5
  58. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.d.ts +1 -1
  59. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.js +1 -13
  60. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/firstPartyHarness.d.ts +113 -39
  61. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/firstPartyHarness.js +146 -70
  62. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/firstPartyHarnessChannels.d.ts +21 -33
  63. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/firstPartyHarnessChannels.js +24 -49
  64. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/firstPartyHarnessHome.d.ts +19 -7
  65. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/firstPartyHarnessHome.js +27 -11
  66. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/harnessTrust.d.ts +9 -5
  67. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/harnessTrust.js +9 -5
  68. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.d.ts +1 -1
  69. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.js +1 -4
  70. package/dist/node_modules/@skrr-ai/auth-core/package.json +1 -1
  71. package/dist/node_modules/@skrr-ai/data-provider/index.js +3855 -3779
  72. package/dist/node_modules/@skrr-ai/data-provider/package.json +1 -1
  73. package/oclif.manifest.json +26935 -26810
  74. package/package.json +2 -2
@@ -1,10 +1,11 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const core_1 = require("@oclif/core");
4
+ const auth_core_1 = require("@skrr-ai/auth-core");
4
5
  const base_command_1 = require("../../base-command");
5
6
  const balance_1 = require("../../lib/balance");
6
7
  class BalanceOverage extends base_command_1.BaseCommand {
7
- static description = 'Enable or disable personal Sky Code overage after included allowance is exhausted';
8
+ static description = `Enable or disable personal ${auth_core_1.FIRST_PARTY_HARNESS.displayName} overage after included allowance is exhausted`;
8
9
  static examples = [
9
10
  '<%= config.bin %> balance overage enabled',
10
11
  '<%= config.bin %> balance overage disabled',
@@ -12,7 +13,7 @@ class BalanceOverage extends base_command_1.BaseCommand {
12
13
  ];
13
14
  static args = {
14
15
  state: core_1.Args.string({
15
- description: 'Whether personal stored balance may pay for Sky Code overage',
16
+ description: `Whether personal stored balance may pay for ${auth_core_1.FIRST_PARTY_HARNESS.displayName} overage`,
16
17
  required: true,
17
18
  options: ['enabled', 'disabled'],
18
19
  ignoreStdin: true,
@@ -34,7 +35,7 @@ class BalanceOverage extends base_command_1.BaseCommand {
34
35
  this.log(JSON.stringify({ ownerType: 'user', overageEnabled: result?.enabled === true }, null, 2));
35
36
  return;
36
37
  }
37
- this.log(`Sky Code overage ${result?.enabled ? 'enabled' : 'disabled'} for your personal billing account.`);
38
+ this.log(`${auth_core_1.FIRST_PARTY_HARNESS.displayName} overage ${result?.enabled ? 'enabled' : 'disabled'} for your personal billing account.`);
38
39
  this.log(result?.enabled
39
40
  ? 'New admissions may use stored balance after included allowance is exhausted.'
40
41
  : 'New admissions will stop when included allowance is exhausted.');
@@ -2,12 +2,13 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.renderSkyCodePlan = renderSkyCodePlan;
4
4
  const core_1 = require("@oclif/core");
5
+ const auth_core_1 = require("@skrr-ai/auth-core");
5
6
  const data_provider_1 = require("@skrr-ai/data-provider");
6
7
  const base_command_1 = require("../../base-command");
7
8
  const balance_1 = require("../../lib/balance");
8
9
  const web_url_1 = require("../../lib/web-url");
9
10
  class BalancePlan extends base_command_1.BaseCommand {
10
- static description = 'Show your Sky Code plan, renewal state, available tiers, and the matching lifecycle page';
11
+ static description = `Show your ${auth_core_1.FIRST_PARTY_HARNESS.displayName} plan, renewal state, available tiers, and the matching lifecycle page`;
11
12
  static examples = [
12
13
  '<%= config.bin %> balance plan',
13
14
  '<%= config.bin %> balance plan --json',
@@ -33,7 +34,7 @@ class BalancePlan extends base_command_1.BaseCommand {
33
34
  }
34
35
  exports.default = BalancePlan;
35
36
  function renderSkyCodePlan(subscription, lifecycleUrl, log) {
36
- log(`Sky Code plan — ${subscription.tierName}`);
37
+ log(`${auth_core_1.FIRST_PARTY_HARNESS.displayName} plan — ${subscription.tierName}`);
37
38
  log(` Status ${subscription.status}`);
38
39
  log(` Price $${subscription.monthlyPriceUsd}/month`);
39
40
  log(` Current period ${subscription.currentPeriodStart ?? 'not started'}`);
@@ -43,10 +44,13 @@ function renderSkyCodePlan(subscription, lifecycleUrl, log) {
43
44
  log(` Concurrent sessions ${subscription.concurrentSessions ?? 'not reported'}`);
44
45
  log(` Requests per minute ${subscription.rateLimitPerMin}`);
45
46
  log('');
46
- log('Available Sky Code plans:');
47
- for (const key of data_provider_1.SKY_CODE_PLAN_KEYS.filter((plan) => plan !== 'free')) {
48
- const plan = data_provider_1.SKY_CODE_PLAN_CATALOG[key];
49
- log(` ${plan.displayName.padEnd(18)} $${plan.monthlyPriceUsd}/month · ` +
47
+ log(`Available ${auth_core_1.FIRST_PARTY_HARNESS.displayName} plans:`);
48
+ const paidPlans = data_provider_1.SKY_CODE_PLAN_KEYS.filter((plan) => plan !== 'free').map((key) => data_provider_1.SKY_CODE_PLAN_CATALOG[key]);
49
+ // Padded to the longest name rather than a fixed width: plan names embed the
50
+ // product's display name, whose length is not this renderer's to assume.
51
+ const nameWidth = Math.max(...paidPlans.map((plan) => plan.displayName.length)) + 1;
52
+ for (const plan of paidPlans) {
53
+ log(` ${plan.displayName.padEnd(nameWidth)} $${plan.monthlyPriceUsd}/month · ` +
50
54
  `${(0, data_provider_1.usageMicrosToDisplayCredits)(plan.includedUsageMicros).toLocaleString()} monthly credits · ` +
51
55
  `${(0, data_provider_1.usageMicrosToDisplayCredits)(plan.fiveHourUsageMicros).toLocaleString()} per 5 hours · ` +
52
56
  `${plan.concurrentSessions} sessions · ${plan.requestsPerMinute} RPM`);
@@ -2,10 +2,11 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.renderSkyCodeUsageEvents = renderSkyCodeUsageEvents;
4
4
  const core_1 = require("@oclif/core");
5
+ const auth_core_1 = require("@skrr-ai/auth-core");
5
6
  const base_command_1 = require("../../../base-command");
6
7
  const balance_1 = require("../../../lib/balance");
7
8
  class BalanceUsageEvents extends base_command_1.BaseCommand {
8
- static description = 'List the Sky Code charges and run/session attribution behind aggregate allowance usage';
9
+ static description = `List the ${auth_core_1.FIRST_PARTY_HARNESS.displayName} charges and run/session attribution behind aggregate allowance usage`;
9
10
  static examples = [
10
11
  '<%= config.bin %> balance usage events',
11
12
  '<%= config.bin %> balance usage events --limit 20 --json',
@@ -44,7 +45,7 @@ class BalanceUsageEvents extends base_command_1.BaseCommand {
44
45
  }
45
46
  exports.default = BalanceUsageEvents;
46
47
  function renderSkyCodeUsageEvents(events, log) {
47
- log(`Sky Code usage events · ${events.ownerType} ${events.ownerId}`);
48
+ log(`${auth_core_1.FIRST_PARTY_HARNESS.displayName} usage events · ${events.ownerType} ${events.ownerId}`);
48
49
  log(` Window ${events.from} to ${events.to}`);
49
50
  if (events.items.length === 0) {
50
51
  log(' No usage events in this window.');
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.renderSkyCodeUsage = renderSkyCodeUsage;
4
4
  const core_1 = require("@oclif/core");
5
+ const auth_core_1 = require("@skrr-ai/auth-core");
5
6
  const base_command_1 = require("../../base-command");
6
7
  const balance_1 = require("../../lib/balance");
7
8
  /**
@@ -40,7 +41,7 @@ class BalanceUsage extends base_command_1.BaseCommand {
40
41
  }
41
42
  exports.default = BalanceUsage;
42
43
  function renderSkyCodeUsage(usage, log) {
43
- log(`Sky Code ${usage.plan ?? 'current'} plan · ${usage.ownerType} ${usage.ownerId}`);
44
+ log(`${auth_core_1.FIRST_PARTY_HARNESS.displayName} ${usage.plan ?? 'current'} plan · ${usage.ownerType} ${usage.ownerId}`);
44
45
  log(` Monthly used ${(0, balance_1.formatCreditsWithUsd)(usage.monthly.usedUsageMicros)}`);
45
46
  log(` Monthly reserved ${(0, balance_1.formatCreditsWithUsd)(usage.monthly.reservedUsageMicros)}`);
46
47
  log(` Monthly remaining ${(0, balance_1.formatCreditsWithUsd)(usage.monthly.remainingUsageMicros)}`);
@@ -23,8 +23,9 @@ class BrowserInstall extends core_1.Command {
23
23
  async run() {
24
24
  const { flags } = await this.parse(BrowserInstall);
25
25
  const args = ['install'];
26
- // Any spelling of the first-party harness is accepted here and handed to the
27
- // daemon in the spelling every installed daemon understands.
26
+ // Any ACCEPTED spelling of the first-party harness is handed to the daemon in
27
+ // the wire spelling; anything else — a retired spelling included — is forwarded
28
+ // unchanged for the daemon's own parser to accept or refuse.
28
29
  if (flags.harness)
29
30
  args.push('--harness', (0, harness_provider_input_1.daemonHarnessArgument)(flags.harness));
30
31
  if (flags.config)
@@ -23,8 +23,9 @@ class BrowserUninstall extends core_1.Command {
23
23
  async run() {
24
24
  const { flags } = await this.parse(BrowserUninstall);
25
25
  const args = ['uninstall'];
26
- // Any spelling of the first-party harness is accepted here and handed to the
27
- // daemon in the spelling every installed daemon understands.
26
+ // Any ACCEPTED spelling of the first-party harness is handed to the daemon in
27
+ // the wire spelling; anything else — a retired spelling included — is forwarded
28
+ // unchanged for the daemon's own parser to accept or refuse.
28
29
  if (flags.harness)
29
30
  args.push('--harness', (0, harness_provider_input_1.daemonHarnessArgument)(flags.harness));
30
31
  if (flags.config)
@@ -201,8 +201,9 @@ class CodeInstall extends base_command_1.BaseCommand {
201
201
  async installerNote(daemonId) {
202
202
  try {
203
203
  const res = await (0, api_fetch_1.apiFetch)(`/api/daemons/${encodeURIComponent(daemonId)}/cli-installers`, { credential: this.resolvedCredential });
204
- // Any spelling: a daemon lists the provider the way it registered it, and
205
- // one that predates or postdates a rename spells it differently.
204
+ // Any accepted spelling: a daemon lists the provider the way it registered it,
205
+ // and during a rename's alias window one that predates or postdates the flip
206
+ // spells it differently.
206
207
  const row = (res.providers ?? []).find((p) => (0, auth_core_1.isFirstPartyHarnessProvider)(p.provider));
207
208
  const reason = row?.installDisabledReason?.trim();
208
209
  if (!reason)
@@ -46,6 +46,17 @@ export default class HarnessesInstall extends BaseCommand {
46
46
  * reason the daemon already words. Both are answered before anything runs.
47
47
  */
48
48
  private update;
49
+ /**
50
+ * Start an install or update, and survive a gateway that cut the request off.
51
+ *
52
+ * The relay holds the request while the daemon answers, and a gateway can end
53
+ * it first: an update returned HTTP 504 after 21.9s while the job it started
54
+ * ran to success, and the caller was told the outcome was unknown (OSK-10096
55
+ * verifier). The daemon is the only party that knows whether the request
56
+ * arrived, so on a server-side failure we ask it, and follow the job it
57
+ * started rather than reporting a result we do not have.
58
+ */
59
+ private start;
49
60
  /** Follow a job to its end, or report why it could not be followed (and return nothing). */
50
61
  private follow;
51
62
  /** What the daemon says about this provider right now, best effort. */
@@ -74,14 +74,9 @@ class HarnessesInstall extends base_command_1.BaseCommand {
74
74
  await this.update(daemonId, provider, Boolean(flags.json));
75
75
  return;
76
76
  }
77
- let start;
78
- try {
79
- start = await (0, cli_installers_1.startManagedCliInstall)(daemonId, provider, this.resolvedCredential);
80
- }
81
- catch (err) {
82
- this.handleApiError(err);
77
+ const start = await this.start(daemonId, provider, () => (0, cli_installers_1.startManagedCliInstall)(daemonId, provider, this.resolvedCredential), Boolean(flags.json));
78
+ if (!start)
83
79
  return;
84
- }
85
80
  const jobId = start.job?.id;
86
81
  if (!start.started || !jobId) {
87
82
  // The daemon declined and already knows why. Read its own reason off the
@@ -163,14 +158,9 @@ class HarnessesInstall extends base_command_1.BaseCommand {
163
158
  });
164
159
  return;
165
160
  }
166
- let start;
167
- try {
168
- start = await (0, cli_installers_1.startManagedCliUpdate)(daemonId, provider, this.resolvedCredential);
169
- }
170
- catch (err) {
171
- this.handleApiError(err);
161
+ const start = await this.start(daemonId, provider, () => (0, cli_installers_1.startManagedCliUpdate)(daemonId, provider, this.resolvedCredential), json);
162
+ if (!start)
172
163
  return;
173
- }
174
164
  const jobId = start.job?.id;
175
165
  if (!start.started || !jobId) {
176
166
  this.log(start.alreadyRunning
@@ -199,6 +189,35 @@ class HarnessesInstall extends base_command_1.BaseCommand {
199
189
  this.log((0, cli_installers_1.updateOutcomeLine)(job) ??
200
190
  `${provider} updated${job.installedVersion ? ` to ${job.installedVersion}` : ''}.`);
201
191
  }
192
+ /**
193
+ * Start an install or update, and survive a gateway that cut the request off.
194
+ *
195
+ * The relay holds the request while the daemon answers, and a gateway can end
196
+ * it first: an update returned HTTP 504 after 21.9s while the job it started
197
+ * ran to success, and the caller was told the outcome was unknown (OSK-10096
198
+ * verifier). The daemon is the only party that knows whether the request
199
+ * arrived, so on a server-side failure we ask it, and follow the job it
200
+ * started rather than reporting a result we do not have.
201
+ */
202
+ async start(daemonId, provider, begin, json) {
203
+ try {
204
+ return await begin();
205
+ }
206
+ catch (err) {
207
+ const status = err.status ?? 0;
208
+ if (status >= 500 || status === 408) {
209
+ const job = await (0, cli_installers_1.jobStartedDespiteTimeout)(daemonId, provider, this.resolvedCredential);
210
+ if (job?.id) {
211
+ if (!json) {
212
+ this.logToStderr(`The request to ${daemonId} did not come back (HTTP ${status}), but the daemon started job ${job.id}. Following it.`);
213
+ }
214
+ return { started: true, job };
215
+ }
216
+ }
217
+ this.handleApiError(err);
218
+ return undefined;
219
+ }
220
+ }
202
221
  /** Follow a job to its end, or report why it could not be followed (and return nothing). */
203
222
  async follow(daemonId, jobId, verb, json) {
204
223
  const outcome = await (0, cli_installers_1.followManagedCliInstallJob)({
@@ -1,4 +1,5 @@
1
1
  import { BaseCommand } from '../../base-command';
2
+ import { type ManagedCliProviderState } from '../../lib/cli-installers';
2
3
  /**
3
4
  * What each managed provider's state is on one machine.
4
5
  *
@@ -43,3 +44,12 @@ export default class HarnessesInstallers extends BaseCommand {
43
44
  */
44
45
  private daemonIdFor;
45
46
  }
47
+ /**
48
+ * What each installed CLI's update story is, as sentences under the table.
49
+ *
50
+ * Sentences, not a column: an update reason is "the copy inside Devin.app
51
+ * updates with that app", which no column fits. Silent for a daemon that
52
+ * reports no update fields at all — it cannot say, and inventing "no" for it
53
+ * would be worse than nothing.
54
+ */
55
+ export declare function updateNotes(providers: readonly ManagedCliProviderState[], bin: string): string[];
@@ -1,5 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.updateNotes = updateNotes;
3
4
  const core_1 = require("@oclif/core");
4
5
  const base_command_1 = require("../../base-command");
5
6
  const data_provider_1 = require("@skrr-ai/data-provider");
@@ -126,6 +127,14 @@ class HarnessesInstallers extends base_command_1.BaseCommand {
126
127
  for (const note of notes)
127
128
  this.log(` ${note.provider}: ${note.detail}`);
128
129
  }
130
+ // The VERSION column is where a reader asks "can I get a newer one", and
131
+ // the answer lived only in `--json` (OSK-10096 verifier).
132
+ const updateLines = updateNotes(providers, this.config.bin);
133
+ if (updateLines.length > 0) {
134
+ this.log('');
135
+ for (const line of updateLines)
136
+ this.log(` ${line}`);
137
+ }
129
138
  // A provider the platform knows and this daemon did not mention. Without
130
139
  // this it simply is not on the list, which reads as dropped support rather
131
140
  // than as an older daemon — `devin` arrived in the 0.8.45 daemon release, so 0.8.35
@@ -177,3 +186,25 @@ class HarnessesInstallers extends base_command_1.BaseCommand {
177
186
  }
178
187
  }
179
188
  exports.default = HarnessesInstallers;
189
+ /**
190
+ * What each installed CLI's update story is, as sentences under the table.
191
+ *
192
+ * Sentences, not a column: an update reason is "the copy inside Devin.app
193
+ * updates with that app", which no column fits. Silent for a daemon that
194
+ * reports no update fields at all — it cannot say, and inventing "no" for it
195
+ * would be worse than nothing.
196
+ */
197
+ function updateNotes(providers, bin) {
198
+ const lines = [];
199
+ for (const provider of providers) {
200
+ if (provider.updatable === true) {
201
+ lines.push(`${provider.provider}: update it with \`${bin} harnesses install ${provider.provider} --update\``);
202
+ }
203
+ }
204
+ for (const provider of providers) {
205
+ if (provider.updatable === false && provider.installed && provider.updateDisabledReason) {
206
+ lines.push(`${provider.provider}: ${provider.updateDisabledReason}`);
207
+ }
208
+ }
209
+ return lines;
210
+ }
@@ -421,8 +421,9 @@ class HarnessesList extends base_command_1.BaseCommand {
421
421
  // mode. Materialize that default before the request so JSON cannot quietly
422
422
  // return every historical offline row while the table shows seven live ones.
423
423
  const status = flags.online ? 'online' : (flags.status ?? (flags.all ? undefined : 'online'));
424
- // Any spelling of the first-party harness filters the same rows; the server is
425
- // asked for the canonical one, which it matches against every stored spelling.
424
+ // Any accepted spelling of the first-party harness filters the same rows; the
425
+ // server is asked for the canonical one, which it matches against every
426
+ // accepted stored spelling.
426
427
  const provider = flags.provider ? (0, harness_provider_input_1.canonicalHarnessInput)(flags.provider) : undefined;
427
428
  const params = new URLSearchParams();
428
429
  const serverStatus = serverStatusParam(status);
@@ -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>;