@skrr-ai/cli 0.1.86 → 0.1.88

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 (103) hide show
  1. package/bin/run.js +11 -0
  2. package/dist/base-command.js +1 -0
  3. package/dist/commands/agents/chat.d.ts +58 -1
  4. package/dist/commands/agents/chat.js +206 -4
  5. package/dist/commands/computer/index.d.ts +19 -0
  6. package/dist/commands/computer/index.js +37 -0
  7. package/dist/commands/labels/create.d.ts +1 -0
  8. package/dist/commands/labels/create.js +2 -0
  9. package/dist/commands/labels/restore.d.ts +1 -0
  10. package/dist/commands/labels/restore.js +6 -3
  11. package/dist/commands/labels/update.d.ts +1 -0
  12. package/dist/commands/labels/update.js +2 -0
  13. package/dist/commands/machines/computer-adoption.d.ts +15 -0
  14. package/dist/commands/machines/computer-adoption.js +33 -0
  15. package/dist/commands/machines/dedicated/archive.d.ts +26 -0
  16. package/dist/commands/machines/dedicated/archive.js +113 -0
  17. package/dist/commands/machines/dedicated/audit.d.ts +25 -0
  18. package/dist/commands/machines/dedicated/audit.js +113 -0
  19. package/dist/commands/machines/dedicated/computer.d.ts +23 -0
  20. package/dist/commands/machines/dedicated/computer.js +64 -0
  21. package/dist/commands/machines/dedicated/cp.d.ts +2 -0
  22. package/dist/commands/machines/dedicated/cp.js +74 -2
  23. package/dist/commands/machines/dedicated/git-status.d.ts +15 -0
  24. package/dist/commands/machines/dedicated/git-status.js +56 -0
  25. package/dist/commands/machines/dedicated/index.js +15 -1
  26. package/dist/commands/machines/dedicated/ls.d.ts +27 -0
  27. package/dist/commands/machines/dedicated/ls.js +100 -0
  28. package/dist/commands/machines/dedicated/mkdir.d.ts +18 -0
  29. package/dist/commands/machines/dedicated/mkdir.js +57 -0
  30. package/dist/commands/machines/dedicated/mv.d.ts +24 -0
  31. package/dist/commands/machines/dedicated/mv.js +83 -0
  32. package/dist/commands/machines/dedicated/rm.d.ts +26 -0
  33. package/dist/commands/machines/dedicated/rm.js +100 -0
  34. package/dist/commands/machines/dedicated/search.d.ts +24 -0
  35. package/dist/commands/machines/dedicated/search.js +78 -0
  36. package/dist/commands/machines/dedicated/stat.d.ts +22 -0
  37. package/dist/commands/machines/dedicated/stat.js +92 -0
  38. package/dist/commands/machines/hosted/index.js +1 -1
  39. package/dist/commands/machines/hosted/list.js +1 -1
  40. package/dist/commands/machines/hosted/start.js +1 -1
  41. package/dist/commands/machines/services/declare.d.ts +24 -0
  42. package/dist/commands/machines/services/declare.js +73 -0
  43. package/dist/commands/machines/services/index.d.ts +15 -0
  44. package/dist/commands/machines/services/index.js +31 -0
  45. package/dist/commands/machines/services/ls.d.ts +17 -0
  46. package/dist/commands/machines/services/ls.js +60 -0
  47. package/dist/commands/machines/services/withdraw.d.ts +19 -0
  48. package/dist/commands/machines/services/withdraw.js +47 -0
  49. package/dist/commands/machines/share.d.ts +29 -0
  50. package/dist/commands/machines/share.js +100 -0
  51. package/dist/commands/machines/shared.d.ts +19 -0
  52. package/dist/commands/machines/shared.js +62 -0
  53. package/dist/commands/machines/shares.d.ts +16 -0
  54. package/dist/commands/machines/shares.js +69 -0
  55. package/dist/commands/machines/unshare.d.ts +19 -0
  56. package/dist/commands/machines/unshare.js +61 -0
  57. package/dist/commands/tasks/labels/create.d.ts +1 -0
  58. package/dist/commands/tasks/labels/create.js +3 -0
  59. package/dist/commands/views/create.d.ts +1 -0
  60. package/dist/commands/views/create.js +5 -0
  61. package/dist/lib/agent-home-workspace.d.ts +26 -0
  62. package/dist/lib/agent-home-workspace.js +174 -0
  63. package/dist/lib/agentic-stream.d.ts +135 -0
  64. package/dist/lib/agentic-stream.js +341 -6
  65. package/dist/lib/auth-storage.d.ts +4 -0
  66. package/dist/lib/auth-storage.js +33 -0
  67. package/dist/lib/computer-adoption.d.ts +55 -0
  68. package/dist/lib/computer-adoption.js +67 -0
  69. package/dist/lib/computer-consent.d.ts +13 -0
  70. package/dist/lib/computer-consent.js +33 -0
  71. package/dist/lib/computer-files.d.ts +239 -0
  72. package/dist/lib/computer-files.js +707 -0
  73. package/dist/lib/dedicated-copy.d.ts +45 -9
  74. package/dist/lib/dedicated-copy.js +141 -44
  75. package/dist/lib/dedicated-machines.d.ts +49 -0
  76. package/dist/lib/dedicated-machines.js +97 -6
  77. package/dist/lib/keychain.d.ts +1 -0
  78. package/dist/lib/keychain.js +67 -14
  79. package/dist/lib/label-scope.d.ts +12 -0
  80. package/dist/lib/label-scope.js +15 -1
  81. package/dist/lib/machine-audit.d.ts +34 -0
  82. package/dist/lib/machine-audit.js +50 -0
  83. package/dist/lib/machine-grants.d.ts +70 -0
  84. package/dist/lib/machine-grants.js +47 -0
  85. package/dist/lib/machine-services.d.ts +64 -0
  86. package/dist/lib/machine-services.js +60 -0
  87. package/dist/lib/refresh.d.ts +7 -0
  88. package/dist/lib/refresh.js +2 -1
  89. package/dist/lib/task-view-render.d.ts +2 -0
  90. package/dist/lib/task-view-render.js +1 -1
  91. package/dist/lib/views/vocabulary.d.ts +1 -1
  92. package/dist/lib/views/vocabulary.js +2 -1
  93. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/computerWire.d.ts +955 -0
  94. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/computerWire.js +1070 -0
  95. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/daemonToolApproval.js +7 -0
  96. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/computerWire.d.ts +955 -0
  97. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/computerWire.js +1051 -0
  98. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/daemonToolApproval.js +7 -0
  99. package/dist/node_modules/@skrr-ai/auth-core/package.json +11 -1
  100. package/dist/node_modules/@skrr-ai/data-provider/index.js +22143 -20728
  101. package/dist/node_modules/@skrr-ai/data-provider/package.json +1 -1
  102. package/oclif.manifest.json +18855 -17260
  103. package/package.json +2 -2
@@ -0,0 +1,113 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const core_1 = require("@oclif/core");
4
+ const base_command_1 = require("../../../base-command");
5
+ const dedicated_lease_command_1 = require("../../../lib/dedicated-lease-command");
6
+ const dedicated_machines_1 = require("../../../lib/dedicated-machines");
7
+ const dedicated_copy_1 = require("../../../lib/dedicated-copy");
8
+ const computer_files_1 = require("../../../lib/computer-files");
9
+ const LIMIT_MIB = dedicated_machines_1.DEDICATED_FILE_MAX_BYTES / (1024 * 1024);
10
+ /**
11
+ * `skrr machines dedicated archive` — download a folder from the machine as a
12
+ * tar file.
13
+ *
14
+ * The machine builds one staged archive of the folder (prepare), the CLI reads
15
+ * it in chunks (read) and frees it (release — always, including on a failed or
16
+ * cancelled download). The local file is written beside its destination and
17
+ * renamed into place, the same discipline `cp` keeps, so a cancelled download
18
+ * never leaves a partial `<name>.tar` — only the machine's staged copy, which
19
+ * release then removes.
20
+ */
21
+ class DedicatedMachinesArchive extends dedicated_lease_command_1.DedicatedLeaseCommand {
22
+ static summary = 'Download a folder from a Dedicated Runtime machine as a tar file';
23
+ static description = [
24
+ `Downloads one folder as a single tar archive of at most ${LIMIT_MIB} MiB and 10,000 entries. ` +
25
+ 'A destination left off, or one that names a directory, receives <name>.tar.',
26
+ 'The staged archive is the folder as it stood when the command ran — the tree may keep ' +
27
+ 'changing, the archive does not. Symlinks are stored as links and never followed out of ' +
28
+ 'the folder.',
29
+ 'A Ctrl-C removes the local partial and releases the staged copy on the machine (exit 130; ' +
30
+ '143 for SIGTERM, 129 when the terminal closes). Progress is shown on a terminal, but not ' +
31
+ 'with --json or under CI.',
32
+ ].join('\n\n');
33
+ static examples = [
34
+ '<%= config.bin %> machines dedicated archive rtl_abc123 repos/app',
35
+ '<%= config.bin %> machines dedicated archive rtl_abc123 repos/app ./backups/',
36
+ '<%= config.bin %> machines dedicated archive rtl_abc123 repos/app ./app-snapshot.tar --json',
37
+ ];
38
+ static args = {
39
+ lease: core_1.Args.string({
40
+ description: dedicated_lease_command_1.DEDICATED_LEASE_ARG_DESCRIPTION,
41
+ required: true,
42
+ ignoreStdin: true,
43
+ }),
44
+ path: core_1.Args.string({
45
+ description: 'Folder on the machine, relative to /workspace (inside repos/ or user-data/)',
46
+ required: true,
47
+ ignoreStdin: true,
48
+ }),
49
+ destination: core_1.Args.string({
50
+ description: 'Local file, or a directory to receive <name>.tar (default: ./<name>.tar)',
51
+ required: false,
52
+ ignoreStdin: true,
53
+ }),
54
+ };
55
+ static flags = {
56
+ json: core_1.Flags.boolean({ description: 'Output the result as JSON' }),
57
+ };
58
+ async run() {
59
+ this.requireAuth();
60
+ const { args, flags } = await this.parse(DedicatedMachinesArchive);
61
+ const leaseId = await this.resolveLeaseArg(args.lease);
62
+ const scope = this.scopeFrom(flags);
63
+ const progress = !flags.json && process.stderr.isTTY && !(0, dedicated_copy_1.isContinuousIntegration)()
64
+ ? (0, dedicated_copy_1.createCopyProgress)({
65
+ label: 'Downloading',
66
+ write: (text) => process.stderr.write(text),
67
+ })
68
+ : null;
69
+ // Same interrupt contract as `cp`: the first signal asks the download to
70
+ // stop; the partial is removed and the staged archive released.
71
+ const interrupt = (0, dedicated_copy_1.listenForCopyInterrupt)();
72
+ let result;
73
+ try {
74
+ // Once: prepare, every chunk and the release present this generation.
75
+ const generation = await (0, computer_files_1.resolveDedicatedFileGeneration)(leaseId, scope);
76
+ result = await (0, computer_files_1.downloadComputerFileArchive)(leaseId, args.path, args.destination, generation, scope, {
77
+ signal: interrupt.signal,
78
+ onProgress: progress?.update,
79
+ });
80
+ }
81
+ catch (thrown) {
82
+ progress?.clear();
83
+ const interruptedBy = interrupt.interruptedBy();
84
+ const err = interruptedBy ? new dedicated_copy_1.DedicatedCopyCancelledError(interruptedBy) : thrown;
85
+ if (err instanceof dedicated_copy_1.DedicatedCopyCancelledError) {
86
+ this.error(`${err.message} Nothing was written for ${args.path}; the staged archive on the machine is released.`, { exit: err.exitCode, code: err.code });
87
+ }
88
+ const refusal = (0, base_command_1.apiRefusal)(err);
89
+ if (refusal.body?.retryable === true)
90
+ this.handleApiError(err);
91
+ const failure = (0, computer_files_1.describeComputerFileFailure)(err, { bin: this.config.bin, leaseId });
92
+ if (failure)
93
+ this.failWithCliError({ ...failure, exit: 1 });
94
+ if (err instanceof Error && typeof err.status !== 'number') {
95
+ this.error(err.message, { exit: 1, code: 'DEDICATED_RUNTIME_FILE_TRANSFER_FAILED' });
96
+ }
97
+ this.handleApiError(err);
98
+ }
99
+ finally {
100
+ interrupt.dispose();
101
+ }
102
+ progress?.clear();
103
+ if (flags.json) {
104
+ this.log(JSON.stringify(result, null, 2));
105
+ return;
106
+ }
107
+ this.log(`Downloaded ${result.name}.${result.format} (${result.entries} entries, ${result.size} bytes) → ${result.destination}`);
108
+ if (!result.archiveReleased) {
109
+ this.warn('The staged archive could not be released; the machine removes it within the hour.');
110
+ }
111
+ }
112
+ }
113
+ exports.default = DedicatedMachinesArchive;
@@ -0,0 +1,25 @@
1
+ import { DedicatedLeaseCommand } from '../../../lib/dedicated-lease-command';
2
+ import { type ComputerAuditPage } from '../../../lib/machine-audit';
3
+ type AuditEvent = ComputerAuditPage['events'][number];
4
+ /** The actor as ids: the audit record stores no display name. */
5
+ export declare function describeAuditActor(actor: AuditEvent['actor']): string;
6
+ /** What the event touched, as the record says it — a surface, a path, a reason. */
7
+ export declare function describeAuditEvent(event: AuditEvent): string;
8
+ export default class DedicatedMachinesAudit extends DedicatedLeaseCommand {
9
+ static description: string;
10
+ static examples: string[];
11
+ static args: {
12
+ lease: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
13
+ };
14
+ static flags: {
15
+ json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
16
+ kind: import("@oclif/core/lib/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
17
+ actor: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
18
+ since: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
19
+ until: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
20
+ limit: import("@oclif/core/lib/interfaces").OptionFlag<number, import("@oclif/core/lib/interfaces").CustomOptions>;
21
+ cursor: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
22
+ };
23
+ run(): Promise<void>;
24
+ }
25
+ export {};
@@ -0,0 +1,113 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.describeAuditActor = describeAuditActor;
4
+ exports.describeAuditEvent = describeAuditEvent;
5
+ const core_1 = require("@oclif/core");
6
+ const dedicated_lease_command_1 = require("../../../lib/dedicated-lease-command");
7
+ const format_1 = require("../../../lib/format");
8
+ const machine_audit_1 = require("../../../lib/machine-audit");
9
+ /** The actor as ids: the audit record stores no display name. */
10
+ function describeAuditActor(actor) {
11
+ return actor.kind === 'human' ? `person ${actor.userId}` : `agent ${actor.agentId}`;
12
+ }
13
+ /** What the event touched, as the record says it — a surface, a path, a reason. */
14
+ function describeAuditEvent(event) {
15
+ const parts = [];
16
+ if (event.summary)
17
+ parts.push(event.summary);
18
+ else {
19
+ const detail = event.detail ?? {};
20
+ const path = typeof detail.path === 'string' ? detail.path : undefined;
21
+ const reason = typeof detail.reason === 'string' ? detail.reason : undefined;
22
+ if (path)
23
+ parts.push(typeof detail.toPath === 'string' ? `${path} -> ${detail.toPath}` : path);
24
+ if (reason)
25
+ parts.push(reason);
26
+ }
27
+ if (event.surfaceId)
28
+ parts.push(`[${event.surfaceKind ?? 'surface'} ${event.surfaceId}]`);
29
+ return parts.join(' ') || '-';
30
+ }
31
+ class DedicatedMachinesAudit extends dedicated_lease_command_1.DedicatedLeaseCommand {
32
+ static description = "Read a machine's audit record — who changed control, wrote files, opened, closed or shared a surface, changed a grant or used power — long after the live Activity window has rolled off";
33
+ static examples = [
34
+ '<%= config.bin %> machines dedicated audit <lease-id>',
35
+ '<%= config.bin %> machines dedicated audit <lease-id> --kind control_changed --since 14d --until 7d',
36
+ '<%= config.bin %> machines dedicated audit <lease-id> --actor <user-id> --json',
37
+ '<%= config.bin %> machines dedicated audit <lease-id> --cursor <next-cursor>',
38
+ ];
39
+ static args = {
40
+ lease: core_1.Args.string({
41
+ ignoreStdin: true,
42
+ description: dedicated_lease_command_1.DEDICATED_LEASE_ARG_DESCRIPTION,
43
+ required: true,
44
+ }),
45
+ };
46
+ static flags = {
47
+ json: core_1.Flags.boolean({ description: 'Output as JSON' }),
48
+ kind: core_1.Flags.string({
49
+ description: `Only this kind; repeat for several (${machine_audit_1.COMPUTER_AUDIT_KINDS.join(', ')})`,
50
+ multiple: true,
51
+ options: [...machine_audit_1.COMPUTER_AUDIT_KINDS],
52
+ }),
53
+ actor: core_1.Flags.string({ description: 'Only events this person (user id) did' }),
54
+ since: core_1.Flags.string({
55
+ description: 'Only events at or after this time: 2026-09-24, an ISO time, or an age like 7d',
56
+ }),
57
+ until: core_1.Flags.string({
58
+ description: 'Only events before this time: 2026-09-24, an ISO time, or an age like 7d',
59
+ }),
60
+ limit: core_1.Flags.integer({ description: 'Events per page (1-200)', default: 50, min: 1, max: 200 }),
61
+ cursor: core_1.Flags.string({ description: 'Continue from the `next` cursor of a previous page' }),
62
+ };
63
+ async run() {
64
+ this.requireAuth();
65
+ const { args, flags } = await this.parse(DedicatedMachinesAudit);
66
+ const leaseId = await this.resolveLeaseArg(args.lease);
67
+ let since;
68
+ let until;
69
+ try {
70
+ since = flags.since ? (0, machine_audit_1.parseAuditTime)(flags.since, '--since') : undefined;
71
+ until = flags.until ? (0, machine_audit_1.parseAuditTime)(flags.until, '--until') : undefined;
72
+ }
73
+ catch (err) {
74
+ this.error(err instanceof Error ? err.message : String(err), { exit: 2 });
75
+ }
76
+ const page = await this.callDedicated(() => (0, machine_audit_1.listMachineAudit)(`lease:${leaseId}`, {
77
+ kinds: flags.kind,
78
+ actorUserId: flags.actor,
79
+ since,
80
+ until,
81
+ cursor: flags.cursor,
82
+ limit: flags.limit,
83
+ }));
84
+ if (flags.json) {
85
+ this.log(JSON.stringify(page, null, 2));
86
+ return;
87
+ }
88
+ const events = page?.events ?? [];
89
+ if (events.length === 0) {
90
+ // An empty page is "nothing matched", never "nothing happened": the
91
+ // record is kept 400 days and a grantee is not shown grant changes.
92
+ this.log(flags.kind || flags.actor || flags.since || flags.until
93
+ ? 'No audit events match those filters.'
94
+ : 'No audit events recorded for this machine.');
95
+ return;
96
+ }
97
+ (0, format_1.renderTable)(events.map((event) => ({
98
+ when: (0, format_1.localStamp)(event.occurredAt),
99
+ actor: (0, format_1.formatCell)(describeAuditActor(event.actor)),
100
+ kind: (0, format_1.formatCell)(event.kind),
101
+ what: (0, format_1.formatCell)(describeAuditEvent(event)),
102
+ })), [
103
+ { key: 'when', header: `WHEN (${(0, format_1.localZone)()})` },
104
+ { key: 'actor', header: 'ACTOR', verbatim: true },
105
+ { key: 'kind', header: 'KIND' },
106
+ { key: 'what', header: 'WHAT', verbatim: true },
107
+ ], (line) => this.log(line));
108
+ if (page?.nextCursor) {
109
+ this.log(`More (older) events: re-run with --cursor ${page.nextCursor}`);
110
+ }
111
+ }
112
+ }
113
+ exports.default = DedicatedMachinesAudit;
@@ -0,0 +1,23 @@
1
+ import { DedicatedLeaseCommand } from '../../../lib/dedicated-lease-command';
2
+ /**
3
+ * `skrr machines dedicated computer` — open the machine's computer: its
4
+ * terminals, browser and files as one desktop of windows (design §11, §17).
5
+ *
6
+ * The page is `/machines/<machineId>/computer`, where the machine id is the
7
+ * inventory's `lease:<leaseId>`. The command prints that URL (always, on
8
+ * stdout) and opens it unless stdout is not a terminal or --json was passed —
9
+ * the same open contract `projects open` and the other entity opens keep.
10
+ */
11
+ export default class DedicatedMachinesComputer extends DedicatedLeaseCommand {
12
+ static summary: string;
13
+ static examples: string[];
14
+ static args: {
15
+ lease: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
16
+ };
17
+ static flags: {
18
+ web: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
19
+ app: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
20
+ json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
21
+ };
22
+ run(): Promise<void>;
23
+ }
@@ -0,0 +1,64 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const core_1 = require("@oclif/core");
4
+ const dedicated_lease_command_1 = require("../../../lib/dedicated-lease-command");
5
+ const entity_open_1 = require("../../../lib/entity-open");
6
+ const dedicated_machines_1 = require("../../../lib/dedicated-machines");
7
+ /**
8
+ * `skrr machines dedicated computer` — open the machine's computer: its
9
+ * terminals, browser and files as one desktop of windows (design §11, §17).
10
+ *
11
+ * The page is `/machines/<machineId>/computer`, where the machine id is the
12
+ * inventory's `lease:<leaseId>`. The command prints that URL (always, on
13
+ * stdout) and opens it unless stdout is not a terminal or --json was passed —
14
+ * the same open contract `projects open` and the other entity opens keep.
15
+ */
16
+ class DedicatedMachinesComputer extends dedicated_lease_command_1.DedicatedLeaseCommand {
17
+ static summary = 'Open a Dedicated Runtime machine’s computer in the browser or skrr Desktop';
18
+ static examples = [
19
+ '<%= config.bin %> machines dedicated computer rtl_abc123',
20
+ '<%= config.bin %> machines dedicated computer rtl_abc123 --app',
21
+ '<%= config.bin %> machines dedicated computer rtl_abc123 --json',
22
+ ];
23
+ static args = {
24
+ lease: core_1.Args.string({
25
+ description: dedicated_lease_command_1.DEDICATED_LEASE_ARG_DESCRIPTION,
26
+ required: true,
27
+ ignoreStdin: true,
28
+ }),
29
+ };
30
+ static flags = {
31
+ web: core_1.Flags.boolean({ char: 'w', description: 'Open in the browser (default)' }),
32
+ app: core_1.Flags.boolean({ char: 'a', description: 'Open in skrr Desktop (macOS)' }),
33
+ json: core_1.Flags.boolean({ description: 'Print the canonical URL as JSON without opening it' }),
34
+ };
35
+ async run() {
36
+ this.requireAuth();
37
+ const { args, flags } = await this.parse(DedicatedMachinesComputer);
38
+ const leaseId = await this.resolveLeaseArg(args.lease);
39
+ // The lease read doubles as the existence check: an id that resolves to
40
+ // nothing is refused before a browser opens on a dead page.
41
+ const response = await this.callDedicated(() => (0, dedicated_machines_1.getDedicatedRuntime)(leaseId, this.scopeFrom(flags)));
42
+ if (!response?.lease) {
43
+ this.error('Dedicated Runtime lease not found.', { exit: 1 });
44
+ }
45
+ const url = (0, dedicated_machines_1.dedicatedComputerUrl)(this.cliConfig.baseURL, leaseId);
46
+ if (!url) {
47
+ this.error(`No computer page exists for ${leaseId}.`, { exit: 1 });
48
+ }
49
+ const result = await (0, entity_open_1.openEntity)({
50
+ baseURL: this.cliConfig.baseURL,
51
+ entity: { url },
52
+ urlFor: () => null,
53
+ target: flags.app ? 'app' : 'web',
54
+ json: Boolean(flags.json),
55
+ });
56
+ if (flags.json) {
57
+ this.log(JSON.stringify(result, null, 2));
58
+ return;
59
+ }
60
+ const where = result.target === 'app' ? 'skrr Desktop' : 'browser';
61
+ this.log(result.opened ? `Opened ${where}: ${result.url}` : result.url);
62
+ }
63
+ }
64
+ exports.default = DedicatedMachinesComputer;
@@ -17,6 +17,8 @@ export default class DedicatedMachinesCp extends BaseCommand {
17
17
  };
18
18
  static flags: {
19
19
  workspace: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
20
+ overwrite: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
21
+ 'if-tag': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
20
22
  json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
21
23
  };
22
24
  run(): Promise<void>;
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const core_1 = require("@oclif/core");
4
4
  const base_command_1 = require("../../../base-command");
5
+ const computer_files_1 = require("../../../lib/computer-files");
5
6
  const dedicated_machines_1 = require("../../../lib/dedicated-machines");
6
7
  const dedicated_copy_1 = require("../../../lib/dedicated-copy");
7
8
  const LIMIT_MIB = dedicated_machines_1.DEDICATED_FILE_MAX_BYTES / (1024 * 1024);
@@ -26,6 +27,12 @@ class DedicatedMachinesCp extends base_command_1.BaseCommand {
26
27
  'machine is created.',
27
28
  'As with cp and scp, a destination that ends in / or is an existing directory receives the file ' +
28
29
  'under its source name.',
30
+ 'Copying to the machine never replaces a file unasked. A destination that does not exist is created, ' +
31
+ 'and the copy commits only if the name is still free. A destination that exists is refused before ' +
32
+ 'anything is sent, unless --overwrite is given (it replaces the version found when the copy starts, ' +
33
+ 'and says which) or --if-tag <tag> (it replaces exactly the version `stat` showed you). Either way, a ' +
34
+ 'destination that changes during the copy is not replaced. Every request of one copy is bound to the ' +
35
+ 'machine as it was when the copy started: a machine replaced meanwhile refuses the rest.',
29
36
  'Files marked executable keep owner execute permission on the copy. Other permissions use ' +
30
37
  'destination defaults; source special permission bits are not copied.',
31
38
  'Neither side is left half-written: the file is staged beside its destination and renamed into ' +
@@ -36,6 +43,8 @@ class DedicatedMachinesCp extends base_command_1.BaseCommand {
36
43
  static examples = [
37
44
  '<%= config.bin %> machines dedicated cp ./dist/app.tar.gz rtl_abc123:repos/app/app.tar.gz',
38
45
  '<%= config.bin %> machines dedicated cp ./notes.md rtl_abc123:user-data/',
46
+ '<%= config.bin %> machines dedicated cp ./build.zip rtl_abc123:repos/app/build.zip --overwrite',
47
+ '<%= config.bin %> machines dedicated cp ./build.zip rtl_abc123:repos/app/build.zip --if-tag 12345:3182:1761234567890000000',
39
48
  '<%= config.bin %> machines dedicated cp rtl_abc123:repos/app/coverage/report.html ./report.html',
40
49
  ];
41
50
  // `ignoreStdin: true` on both, per the CLI's own hygiene rule: without it oclif
@@ -55,6 +64,12 @@ class DedicatedMachinesCp extends base_command_1.BaseCommand {
55
64
  };
56
65
  static flags = {
57
66
  workspace: core_1.Flags.string({ description: 'Workspace scope' }),
67
+ overwrite: core_1.Flags.boolean({
68
+ description: 'Copying to the machine: replace an existing destination (the version found when the copy starts)',
69
+ }),
70
+ 'if-tag': core_1.Flags.string({
71
+ description: 'Copying to the machine: replace the destination only if it still has this tag — the <inode>:<size>:<mtimeNs> `stat` prints',
72
+ }),
58
73
  json: core_1.Flags.boolean({ description: 'Output the result as JSON' }),
59
74
  };
60
75
  async run() {
@@ -68,6 +83,23 @@ class DedicatedMachinesCp extends base_command_1.BaseCommand {
68
83
  this.error(err.message, { exit: 2 });
69
84
  }
70
85
  const scope = { workspaceId: flags.workspace };
86
+ if (plan.direction === 'download' && (flags.overwrite || flags['if-tag'] !== undefined)) {
87
+ this.error('--overwrite and --if-tag apply when copying to the machine.', { exit: 2 });
88
+ }
89
+ let ifTag;
90
+ if (flags['if-tag'] !== undefined) {
91
+ try {
92
+ ifTag = (0, computer_files_1.parseComputerFileTag)(flags['if-tag']);
93
+ }
94
+ catch (err) {
95
+ this.error(err.message.replace(/--tag/g, '--if-tag'), { exit: 2 });
96
+ }
97
+ if (ifTag.contents !== undefined) {
98
+ this.error('--if-tag names a file version; a folder contents tag cannot be replaced.', {
99
+ exit: 2,
100
+ });
101
+ }
102
+ }
71
103
  // Progress is drawn on stderr, for a person at a terminal: never into a
72
104
  // --json document, and never into a CI log as a stream of carriage returns.
73
105
  const progress = !flags.json && process.stderr.isTTY && !(0, dedicated_copy_1.isContinuousIntegration)()
@@ -84,13 +116,24 @@ class DedicatedMachinesCp extends base_command_1.BaseCommand {
84
116
  let result;
85
117
  try {
86
118
  const { signal } = interrupt;
119
+ // Once, before the first file call: every request of this copy presents
120
+ // this generation, so a machine replaced mid-copy refuses the rest.
121
+ const generation = await (0, computer_files_1.resolveDedicatedFileGeneration)(plan.leaseId, scope);
87
122
  result =
88
123
  plan.direction === 'upload'
89
124
  ? await (0, dedicated_copy_1.uploadToDedicatedRuntime)(plan.localPath, plan.leaseId, plan.remotePath, scope, {
125
+ generation,
90
126
  signal,
91
127
  onProgress,
128
+ overwrite: flags.overwrite === true,
129
+ ...(ifTag ? { ifTag } : {}),
130
+ // Said before a byte is sent, so the person knows what is being replaced.
131
+ onReplace: (destination, tag) => {
132
+ progress?.clear();
133
+ process.stderr.write(`Replacing ${destination} (tag ${(0, computer_files_1.computerFileTagString)(tag)})\n`);
134
+ },
92
135
  })
93
- : await (0, dedicated_copy_1.downloadFromDedicatedRuntime)(plan.leaseId, plan.remotePath, plan.localPath, scope, { signal, onProgress });
136
+ : await (0, dedicated_copy_1.downloadFromDedicatedRuntime)(plan.leaseId, plan.remotePath, plan.localPath, scope, { generation, signal, onProgress });
94
137
  }
95
138
  catch (thrown) {
96
139
  progress?.clear();
@@ -107,6 +150,28 @@ class DedicatedMachinesCp extends base_command_1.BaseCommand {
107
150
  if (err instanceof dedicated_copy_1.DedicatedCopyError) {
108
151
  this.error(err.message, { exit: 1, code: err.code });
109
152
  }
153
+ if (err instanceof computer_files_1.DedicatedFileGenerationUnavailableError) {
154
+ this.error(err.message, { exit: 1, code: err.code });
155
+ }
156
+ // The machine was replaced mid-copy, or its image cannot fence the copy:
157
+ // the computer vocabulary says what to do about either.
158
+ const computerCode = (0, computer_files_1.computerFileRefusalInfo)(err)?.code;
159
+ if (computerCode === 'stale_generation' || computerCode === 'capability_missing') {
160
+ const failure = (0, computer_files_1.describeComputerFileFailure)(err, {
161
+ bin: this.config.bin,
162
+ leaseId: plan.leaseId,
163
+ });
164
+ if (failure) {
165
+ const notReplaced = plan.direction === 'upload'
166
+ ? ` ${plan.leaseId}:${plan.remotePath} was not changed.`
167
+ : ` Nothing was written to ${plan.localPath}.`;
168
+ this.failWithCliError({
169
+ ...failure,
170
+ message: `${failure.message}${notReplaced}`,
171
+ exit: 1,
172
+ });
173
+ }
174
+ }
110
175
  // Keep the API status, request id and explicit retry decision together.
111
176
  if ((0, base_command_1.apiRefusal)(err).body?.retryable === true)
112
177
  this.handleApiError(err);
@@ -129,7 +194,14 @@ class DedicatedMachinesCp extends base_command_1.BaseCommand {
129
194
  this.log(JSON.stringify({ direction: plan.direction, ...result }, null, 2));
130
195
  return;
131
196
  }
132
- this.log(`${plan.direction === 'upload' ? 'Uploaded' : 'Downloaded'} ${result.bytes} bytes → ${result.destination}`);
197
+ if (plan.direction === 'upload' && result.alreadyCommitted) {
198
+ this.log(`Already copied: ${result.destination} (an earlier attempt committed it)`);
199
+ return;
200
+ }
201
+ const replaced = result.replacedTag
202
+ ? ` (replaced the version tagged ${result.replacedTag})`
203
+ : '';
204
+ this.log(`${plan.direction === 'upload' ? 'Uploaded' : 'Downloaded'} ${result.bytes} bytes → ${result.destination}${replaced}`);
133
205
  }
134
206
  }
135
207
  exports.default = DedicatedMachinesCp;
@@ -0,0 +1,15 @@
1
+ import { DedicatedLeaseCommand } from '../../../lib/dedicated-lease-command';
2
+ /** `skrr machines dedicated git-status` — git marks of one folder's entries. */
3
+ export default class DedicatedMachinesGitStatus extends DedicatedLeaseCommand {
4
+ static summary: string;
5
+ static description: string;
6
+ static examples: string[];
7
+ static args: {
8
+ lease: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
9
+ path: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
10
+ };
11
+ static flags: {
12
+ json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
13
+ };
14
+ run(): Promise<void>;
15
+ }
@@ -0,0 +1,56 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const core_1 = require("@oclif/core");
4
+ const dedicated_lease_command_1 = require("../../../lib/dedicated-lease-command");
5
+ const computer_files_1 = require("../../../lib/computer-files");
6
+ /** `skrr machines dedicated git-status` — git marks of one folder's entries. */
7
+ class DedicatedMachinesGitStatus extends dedicated_lease_command_1.DedicatedLeaseCommand {
8
+ static summary = 'Show git marks for a folder on a Dedicated Runtime machine';
9
+ static description = 'Prints the git mark (modified, added, untracked, ignored, conflicted) of each entry of one ' +
10
+ 'folder, as the computer’s Files window decorates them. A folder outside a repository is ' +
11
+ 'said so; a status that does not finish in time offers no marks rather than a partial set. ' +
12
+ 'Available on dedicated machines, not on a personal machine.';
13
+ static examples = [
14
+ '<%= config.bin %> machines dedicated git-status rtl_abc123 repos/app',
15
+ '<%= config.bin %> machines dedicated git-status rtl_abc123 repos/app --json',
16
+ ];
17
+ static args = {
18
+ lease: core_1.Args.string({
19
+ description: dedicated_lease_command_1.DEDICATED_LEASE_ARG_DESCRIPTION,
20
+ required: true,
21
+ ignoreStdin: true,
22
+ }),
23
+ path: core_1.Args.string({
24
+ description: 'Folder on the machine, relative to /workspace',
25
+ required: true,
26
+ ignoreStdin: true,
27
+ }),
28
+ };
29
+ static flags = {
30
+ json: core_1.Flags.boolean({ description: 'Output the result as JSON' }),
31
+ };
32
+ async run() {
33
+ this.requireAuth();
34
+ const { args, flags } = await this.parse(DedicatedMachinesGitStatus);
35
+ const leaseId = await this.resolveLeaseArg(args.lease);
36
+ let result;
37
+ try {
38
+ const scope = this.scopeFrom(flags);
39
+ const generation = await (0, computer_files_1.resolveDedicatedFileGeneration)(leaseId, scope);
40
+ result = await (0, computer_files_1.gitStatusComputerFiles)(leaseId, { path: args.path, generation }, scope);
41
+ }
42
+ catch (err) {
43
+ const failure = (0, computer_files_1.describeComputerFileFailure)(err, { bin: this.config.bin, leaseId });
44
+ if (failure)
45
+ this.failWithCliError({ ...failure, exit: 1 });
46
+ this.handleApiError(err);
47
+ }
48
+ if (flags.json) {
49
+ this.log(JSON.stringify(result, null, 2));
50
+ return;
51
+ }
52
+ for (const line of (0, computer_files_1.computerFileGitStatusLines)(result))
53
+ this.log(line);
54
+ }
55
+ }
56
+ exports.default = DedicatedMachinesGitStatus;
@@ -19,6 +19,13 @@ class DedicatedMachines extends core_1.Command {
19
19
  '<%= config.bin %> machines dedicated sign-in <lease-id> github',
20
20
  '<%= config.bin %> machines dedicated exec <lease-id> -- git -C repos/app status',
21
21
  '<%= config.bin %> machines dedicated cp ./notes.md <lease-id>:user-data/notes.md',
22
+ '<%= config.bin %> machines dedicated ls <lease-id> repos/app',
23
+ '<%= config.bin %> machines dedicated stat <lease-id> repos/app/package.json',
24
+ '<%= config.bin %> machines dedicated mkdir <lease-id> repos/app/dist',
25
+ '<%= config.bin %> machines dedicated mv <lease-id> repos/a repos/b',
26
+ '<%= config.bin %> machines dedicated rm <lease-id> repos/app/dist --recursive',
27
+ '<%= config.bin %> machines dedicated archive <lease-id> repos/app',
28
+ '<%= config.bin %> machines dedicated computer <lease-id>',
22
29
  '<%= config.bin %> machines dedicated stop <lease-id> --wait',
23
30
  '<%= config.bin %> machines dedicated start <lease-id> --wait',
24
31
  '<%= config.bin %> machines dedicated restart <lease-id>',
@@ -44,6 +51,13 @@ class DedicatedMachines extends core_1.Command {
44
51
  ['sign-in', 'sign in to GitHub CLI, Claude Code or Codex there'],
45
52
  ['exec', 'run one command; output and exit code'],
46
53
  ['cp', 'copy a file to or from the machine'],
54
+ ['ls', 'list a directory on the machine'],
55
+ ['stat', 'one path: type, size, mtime, mode and its tag'],
56
+ ['mkdir', 'create a directory, parents included'],
57
+ ['mv', 'rename or move a path'],
58
+ ['rm', 'delete a file or folder (--recursive)'],
59
+ ['archive', 'download a folder as a tar file'],
60
+ ['computer', 'open the machine’s computer in the browser or skrr Desktop'],
47
61
  ['start', 'start a stopped runtime'],
48
62
  ['stop', 'stop compute and keep the disk'],
49
63
  ['restart', 'restart the instance; files stay'],
@@ -56,7 +70,7 @@ class DedicatedMachines extends core_1.Command {
56
70
  ['service', 'declare workloads the machine keeps running'],
57
71
  ['destroy', 'stop compute; keep or purge the workspace disk'],
58
72
  ];
59
- this.log('skrr Dedicated Runtime commands:');
73
+ this.log('Skrr Dedicated Runtime commands:');
60
74
  for (const [name, summary] of rows) {
61
75
  this.log(` skrr machines dedicated ${name.padEnd(15)} ${summary}`);
62
76
  }
@@ -0,0 +1,27 @@
1
+ import { DedicatedLeaseCommand } from '../../../lib/dedicated-lease-command';
2
+ /**
3
+ * `skrr machines dedicated ls` — list one directory on the machine.
4
+ *
5
+ * Paths are relative to /workspace and, on a guest, confined to `repos/` and
6
+ * `user-data/` — the tenant roots; the workspace root itself is the runtime's
7
+ * and a listing of it is refused `path_withheld`. Entries are sorted by the
8
+ * machine by name bytes; a symlink shows its stored target and is never
9
+ * followed; an entry the machine withholds (deny set, blockedPaths, a
10
+ * sensitive pattern, an OS permission) is shown as withheld, not hidden.
11
+ */
12
+ export default class DedicatedMachinesLs extends DedicatedLeaseCommand {
13
+ static summary: string;
14
+ static description: string;
15
+ static examples: string[];
16
+ static args: {
17
+ lease: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
18
+ path: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
19
+ };
20
+ static flags: {
21
+ limit: import("@oclif/core/lib/interfaces").OptionFlag<number | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
22
+ cursor: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
23
+ all: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
24
+ json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
25
+ };
26
+ run(): Promise<void>;
27
+ }