@skrr-ai/cli 0.1.93 → 0.1.95

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (174) hide show
  1. package/README.md +153 -165
  2. package/bin/run.js +32 -0
  3. package/bin/update-npm-runner.cjs +33 -0
  4. package/bin/update-session.cjs +194 -0
  5. package/bin/update-worker.cjs +208 -0
  6. package/dist/base-command.js +0 -15
  7. package/dist/commands/agents/operating-plan.d.ts +11 -0
  8. package/dist/commands/agents/operating-plan.js +54 -0
  9. package/dist/commands/agents/roles/example.d.ts +11 -0
  10. package/dist/commands/agents/roles/example.js +30 -0
  11. package/dist/commands/{commitments/mode.d.ts → agents/roles/export.d.ts} +5 -5
  12. package/dist/commands/agents/roles/export.js +81 -0
  13. package/dist/commands/agents/roles/install.d.ts +12 -0
  14. package/dist/commands/agents/roles/install.js +48 -0
  15. package/dist/commands/agents/roles/list.d.ts +11 -0
  16. package/dist/commands/agents/roles/list.js +36 -0
  17. package/dist/commands/agents/roles/preview.d.ts +14 -0
  18. package/dist/commands/agents/roles/preview.js +49 -0
  19. package/dist/commands/agents/roles/withdraw.d.ts +13 -0
  20. package/dist/commands/agents/roles/withdraw.js +43 -0
  21. package/dist/commands/code/doctor.d.ts +1 -0
  22. package/dist/commands/code/doctor.js +6 -0
  23. package/dist/commands/code/index.js +4 -0
  24. package/dist/commands/code/install.d.ts +5 -62
  25. package/dist/commands/code/install.js +86 -279
  26. package/dist/commands/code/rollback.d.ts +8 -0
  27. package/dist/commands/code/rollback.js +20 -0
  28. package/dist/commands/code/update.d.ts +8 -37
  29. package/dist/commands/code/update.js +134 -213
  30. package/dist/commands/commitments/{autonomy-requests → cadence-requests}/decide.d.ts +5 -7
  31. package/dist/commands/commitments/{autonomy-requests → cadence-requests}/decide.js +13 -15
  32. package/dist/commands/commitments/cost.d.ts +1 -1
  33. package/dist/commands/commitments/cost.js +1 -1
  34. package/dist/commands/commitments/create.d.ts +0 -7
  35. package/dist/commands/commitments/create.js +9 -98
  36. package/dist/commands/commitments/delivery.js +3 -21
  37. package/dist/commands/commitments/doctor.js +28 -27
  38. package/dist/commands/commitments/effective-policy.js +5 -36
  39. package/dist/commands/commitments/evaluations.d.ts +15 -0
  40. package/dist/commands/commitments/evaluations.js +54 -0
  41. package/dist/commands/commitments/pack/activate.d.ts +11 -0
  42. package/dist/commands/commitments/pack/activate.js +22 -0
  43. package/dist/commands/commitments/pack/bind.js +1 -1
  44. package/dist/commands/commitments/remediate.js +2 -5
  45. package/dist/commands/commitments/show.js +1 -1
  46. package/dist/commands/commitments/update.js +9 -9
  47. package/dist/commands/commitments/wake-capabilities.d.ts +1 -3
  48. package/dist/commands/commitments/wake-capabilities.js +5 -21
  49. package/dist/commands/daemon/install.js +4 -0
  50. package/dist/commands/generate-recovery-code.d.ts +13 -1
  51. package/dist/commands/generate-recovery-code.js +146 -13
  52. package/dist/commands/instructions/guide.d.ts +20 -0
  53. package/dist/commands/instructions/guide.js +84 -0
  54. package/dist/commands/instructions/install.js +57 -33
  55. package/dist/commands/instructions/setup.d.ts +14 -0
  56. package/dist/commands/instructions/setup.js +72 -0
  57. package/dist/commands/integrations/execute.js +1 -1
  58. package/dist/commands/login.d.ts +5 -6
  59. package/dist/commands/login.js +27 -11
  60. package/dist/commands/logout.js +7 -7
  61. package/dist/commands/security/overview.d.ts +11 -0
  62. package/dist/commands/security/overview.js +158 -0
  63. package/dist/commands/spaces/claim.d.ts +12 -0
  64. package/dist/commands/spaces/claim.js +42 -0
  65. package/dist/commands/spaces/ownership.d.ts +12 -0
  66. package/dist/commands/spaces/ownership.js +46 -0
  67. package/dist/commands/tasks/cancel-cascade.js +1 -1
  68. package/dist/commands/tasks/create-tree.d.ts +5 -1
  69. package/dist/commands/tasks/create-tree.js +35 -7
  70. package/dist/commands/tasks/interrupt.js +2 -2
  71. package/dist/commands/tasks/pause-schedule.js +1 -1
  72. package/dist/commands/token/list.d.ts +3 -5
  73. package/dist/commands/token/list.js +99 -57
  74. package/dist/commands/update.d.ts +14 -0
  75. package/dist/commands/update.js +61 -0
  76. package/dist/generated/managed-instruction-guides.json +9 -0
  77. package/dist/hooks/init.d.ts +5 -0
  78. package/dist/hooks/init.js +27 -0
  79. package/dist/lib/commitment-product.d.ts +0 -6
  80. package/dist/lib/commitment-product.js +1 -30
  81. package/dist/lib/commitments.d.ts +9 -53
  82. package/dist/lib/commitments.js +23 -146
  83. package/dist/lib/daemon-installer.d.ts +2 -0
  84. package/dist/lib/daemon-installer.js +100 -24
  85. package/dist/lib/first-party-harness-doctor.js +31 -1
  86. package/dist/lib/first-party-harness-remote-install.d.ts +71 -0
  87. package/dist/lib/first-party-harness-remote-install.js +310 -0
  88. package/dist/lib/first-party-harness-remote-update.d.ts +46 -0
  89. package/dist/lib/first-party-harness-remote-update.js +242 -0
  90. package/dist/lib/first-party-harness-startup-update.d.ts +26 -0
  91. package/dist/lib/first-party-harness-startup-update.js +166 -0
  92. package/dist/lib/first-party-harness-update.d.ts +46 -0
  93. package/dist/lib/first-party-harness-update.js +289 -0
  94. package/dist/lib/first-party-harness.js +7 -4
  95. package/dist/lib/managed-instruction-local.d.ts +12 -0
  96. package/dist/lib/managed-instruction-local.js +58 -0
  97. package/dist/lib/managed-instruction-setup.d.ts +35 -0
  98. package/dist/lib/managed-instruction-setup.js +306 -0
  99. package/dist/lib/memory-sync.d.ts +1 -0
  100. package/dist/lib/memory-sync.js +279 -68
  101. package/dist/lib/northstars.js +1 -1
  102. package/dist/lib/platform-update-local.d.ts +2 -0
  103. package/dist/lib/platform-update-local.js +113 -0
  104. package/dist/lib/platform-update.d.ts +39 -0
  105. package/dist/lib/platform-update.js +84 -0
  106. package/dist/lib/profile-machine-identity-guard.d.ts +2 -0
  107. package/dist/lib/profile-machine-identity-guard.js +13 -0
  108. package/dist/lib/prompt.d.ts +1 -0
  109. package/dist/lib/prompt.js +4 -3
  110. package/dist/lib/roles.d.ts +98 -0
  111. package/dist/lib/roles.js +45 -0
  112. package/dist/lib/task-batch.d.ts +9 -0
  113. package/dist/lib/task-batch.js +94 -17
  114. package/dist/lib/task-instruction-offer.d.ts +1 -4
  115. package/dist/lib/task-instruction-offer.js +3 -129
  116. package/dist/lib/update-check.d.ts +61 -53
  117. package/dist/lib/update-check.js +453 -314
  118. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/commitmentExecutionContract.d.ts +45 -0
  119. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/commitmentExecutionContract.js +550 -0
  120. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/firstPartyHarnessChannels.d.ts +3 -4
  121. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/firstPartyHarnessChannels.js +3 -4
  122. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/firstPartyHarnessReleaseKeys.d.ts +82 -0
  123. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/firstPartyHarnessReleaseKeys.js +105 -0
  124. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/firstPartyHarnessUpdater.d.ts +110 -0
  125. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/firstPartyHarnessUpdater.js +945 -0
  126. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/harnessPermissionControls.d.ts +32 -0
  127. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/harnessPermissionControls.js +62 -0
  128. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.d.ts +2 -0
  129. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.js +4 -1
  130. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/machineUuid.js +35 -5
  131. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/releasePolicy.d.ts +63 -0
  132. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/releasePolicy.js +169 -0
  133. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/releaseVersions.d.ts +2 -0
  134. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/releaseVersions.js +44 -0
  135. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/sessionPermissionAuthority.d.ts +7 -44
  136. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/sessionPermissionAuthority.js +8 -47
  137. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/commitmentExecutionContract.d.ts +45 -0
  138. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/commitmentExecutionContract.js +545 -0
  139. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/firstPartyHarnessChannels.d.ts +3 -4
  140. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/firstPartyHarnessChannels.js +3 -4
  141. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/firstPartyHarnessReleaseKeys.d.ts +82 -0
  142. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/firstPartyHarnessReleaseKeys.js +102 -0
  143. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/firstPartyHarnessUpdater.d.ts +110 -0
  144. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/firstPartyHarnessUpdater.js +923 -0
  145. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/harnessPermissionControls.d.ts +32 -0
  146. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/harnessPermissionControls.js +59 -0
  147. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.d.ts +2 -0
  148. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.js +2 -0
  149. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/machineUuid.js +35 -5
  150. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/releasePolicy.d.ts +63 -0
  151. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/releasePolicy.js +163 -0
  152. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/releaseVersions.d.ts +2 -0
  153. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/releaseVersions.js +41 -0
  154. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/sessionPermissionAuthority.d.ts +7 -44
  155. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/sessionPermissionAuthority.js +8 -47
  156. package/dist/node_modules/@skrr-ai/auth-core/package.json +41 -1
  157. package/dist/node_modules/@skrr-ai/data-provider/index.js +7362 -7232
  158. package/dist/node_modules/@skrr-ai/data-provider/package.json +2 -2
  159. package/docs/v1/README.md +28 -0
  160. package/docs/v1/api.md +55 -0
  161. package/docs/v1/changelog.md +16 -0
  162. package/docs/v1/check-examples.mjs +178 -0
  163. package/docs/v1/examples/api-readonly.mjs +144 -0
  164. package/docs/v1/operations.md +87 -0
  165. package/docs/v1/quickstart.md +71 -0
  166. package/oclif.manifest.json +46381 -45478
  167. package/package.json +16 -11
  168. package/dist/commands/commitments/autonomy.d.ts +0 -30
  169. package/dist/commands/commitments/autonomy.js +0 -67
  170. package/dist/commands/commitments/mode.js +0 -58
  171. package/dist/commands/commitments/pack/promote.d.ts +0 -19
  172. package/dist/commands/commitments/pack/promote.js +0 -46
  173. package/dist/commands/commitments/pack/shadow.d.ts +0 -20
  174. package/dist/commands/commitments/pack/shadow.js +0 -42
@@ -6,9 +6,11 @@ const auth_core_1 = require("@skrr-ai/auth-core");
6
6
  const base_command_1 = require("../base-command");
7
7
  const daemonHandoff_1 = require("../lib/daemonHandoff");
8
8
  const daemon_setup_1 = require("../lib/daemon-setup");
9
+ const managed_instruction_setup_1 = require("../lib/managed-instruction-setup");
9
10
  const config_1 = require("../lib/config");
10
11
  const cli_id_1 = require("../lib/cli-id");
11
12
  const keychain_1 = require("../lib/keychain");
13
+ const profile_machine_identity_guard_1 = require("../lib/profile-machine-identity-guard");
12
14
  const login_1 = require("../lib/login");
13
15
  const device_code_1 = require("../lib/device-code");
14
16
  const api_fetch_1 = require("../lib/api-fetch");
@@ -29,14 +31,13 @@ const prompt_1 = require("../lib/prompt");
29
31
  * Refresh, rotation, family revoke, and the cross-process auth.lock are
30
32
  * all the same substrate the daemon uses (@skrr-ai/auth-core).
31
33
  *
32
- * CI. There is no long-lived token that `skrr` commands accept yet (OSK-13208).
33
- * `skrr create-token` mints an `osk_ci_*` token, and that token only
34
- * bootstraps a DAEMON (`OVERSKY_TOKEN=osk_ci_... skrrd start`): every `skrr`
35
- * command rejects it with 401 (see create-token.ts and base-command.ts). What
36
- * works today is a session minted for the machine with
34
+ * CI. `skrr tokens create` mints an expiring, revocable personal access token
35
+ * (`osk_pat_*`) for SKRR_TOKEN. It does not rotate and is distinct from a
36
+ * per-machine SESSION minted with
37
37
  * `skrr token mint --for <id>`, restored by the `OVERSKY_REFRESH_TOKEN` path
38
38
  * below — whose refresh token rotates on use — or `OVERSKY_AUTH_HELPER`.
39
- * docs/cli/SKRR_AUTH.md ("CI today") states the caveats.
39
+ * docs/cli/SKRR_AUTH.md states credential precedence and revocation scopes.
40
+ * Legacy `skrr create-token` credentials only bootstrap a daemon.
40
41
  *
41
42
  * L13 — non-interactive restore from a refresh token. When
42
43
  * `OVERSKY_REFRESH_TOKEN` is set in the environment, the browser /
@@ -60,15 +61,18 @@ class Login extends base_command_1.BaseCommand {
60
61
  'If the background service answers but refuses, skrr says why before moving on.\n\n' +
61
62
  'What it stores: a refresh session in this profile’s credential store (the macOS Keychain, ' +
62
63
  'or ~/.skrr/cli-auth.json elsewhere) that keeps renewing itself. Plain skrr commands on a ' +
63
- 'computer whose background service is signed in need no login at all: they borrow a ' +
64
- 'short-lived token from it for each run and store nothing (an older background service ' +
65
- 'mints them a stored session instead). On a Dedicated Runtime, where the service only ' +
64
+ 'computer whose background service is signed in can adopt a durable CLI session automatically ' +
65
+ 'unless this profile was explicitly signed out. On a Dedicated Runtime, where the service only ' +
66
66
  'lends short-lived tokens, nothing is stored.\n\n' +
67
67
  'Set OVERSKY_SKIP_DAEMON_BROKER=1 to skip step 1 (--device-code skips it too). After signing ' +
68
68
  'in, skrr also signs the background service in, and on an interactive first login on a ' +
69
69
  'computer without it, offers to install it. Set SKRR_SKIP_DAEMON_HANDOFF=1 to skip that ' +
70
- 'optional setup; it does not skip step 1.\n\n' +
71
- 'With no person to approve a sign-in (CI, a provisioning script): mint a session for that ' +
70
+ 'optional setup; it does not skip step 1. On interactive sign-in, skrr checks managed ' +
71
+ 'instruction setup for coding agents on the verified local daemon. You choose each ' +
72
+ 'provider and its update policy; run `skrr instructions setup` to revisit this later. ' +
73
+ 'Set OVERSKY_DISABLE_INSTRUCTION_OFFER=1 to skip these prompts.\n\n' +
74
+ 'For CI, create an expiring personal access token with `skrr tokens create` and supply it as ' +
75
+ 'SKRR_TOKEN in the secret store; no login is needed. For a persistent machine, mint a session for that ' +
72
76
  'machine with `skrr token mint --for <id>` and run this command there with ' +
73
77
  'OVERSKY_REFRESH_TOKEN and OVERSKY_CLI_ID set. The refresh token rotates when used, so one ' +
74
78
  'minted session serves one machine. Tokens from `skrr create-token` only bring up a ' +
@@ -116,6 +120,11 @@ class Login extends base_command_1.BaseCommand {
116
120
  };
117
121
  async run() {
118
122
  const { flags } = await this.parse(Login);
123
+ if ((0, profile_machine_identity_guard_1.legacyProfileSharesDefaultMachine)()) {
124
+ this.error(`Profile '${(0, keychain_1.getActiveProfile)()}' was created before profiles had separate machine identities. ` +
125
+ "Signing in could replace the default machine's device-key pin. " +
126
+ 'Create a new profile name and sign in there; the existing profile was not changed.', { exit: 78 });
127
+ }
119
128
  if (flags['base-url'] && flags.server && flags['base-url'] !== flags.server) {
120
129
  this.error('--base-url and --server were both provided with different values.', { exit: 2 });
121
130
  }
@@ -289,6 +298,13 @@ class Login extends base_command_1.BaseCommand {
289
298
  this.log(`The local daemon was not signed in (${handoff.detail}). ` +
290
299
  `Run \`${this.config.bin} daemon login\` to finish.`);
291
300
  }
301
+ // Authentication above is complete. The common onboarding helper verifies
302
+ // the daemon's live account/server/profile and handles failures nonfatally.
303
+ await (0, managed_instruction_setup_1.maybeOfferManagedInstructionSetup)({
304
+ trigger: 'login',
305
+ log: (line) => this.log(line),
306
+ waitForLocalReady: handoff.status === 'delivered',
307
+ });
292
308
  }
293
309
  catch (err) {
294
310
  // Ctrl-C while waiting for device-code approval: a person's decision,
@@ -68,13 +68,13 @@ function shouldSkipLocalLogout(input) {
68
68
  * laptop scenario where the user wants every persistent daemon credential
69
69
  * gone immediately, regardless of which machine they're calling from.
70
70
  *
71
- * Architectural note: the L7 server-side has a bulk endpoint
72
- * (`POST /api/auth/sessions/revoke-all`) but it requires a user-scope JWT.
73
- * CLI sessions are minted with `scope: 'cli'` (interactive single-machine
74
- * use), so they cannot directly hit the bulk endpoint — that's by design,
75
- * preventing a leaked CLI credential from kicking the user out of every
76
- * other device. We instead iterate `DELETE /api/daemon-resources/:id`
77
- * which CLI scope CAN hit (per-resource ownership check, not scope check).
71
+ * This implementation iterates `DELETE /api/daemon-resources/:id` rather
72
+ * than the broader `POST /api/auth/sessions/revoke-all` endpoint. Its actual
73
+ * scope is registered daemons plus this CLI family; independent browser/CLI
74
+ * refresh families and PATs are not swept. The auth endpoint's current
75
+ * HUMAN_SCOPES includes CLI, so the historical explanation that CLI scope
76
+ * cannot call it is obsolete. Preserve the disclosed narrower behavior until
77
+ * a separately reviewed authority/UX migration changes it.
78
78
  * N round trips for N daemons; for typical fleet sizes (< 20) this is
79
79
  * sub-second. Each per-daemon DELETE produces its own audit row keyed by
80
80
  * `'user_initiated_per_device'`, which preserves forensic granularity.
@@ -0,0 +1,11 @@
1
+ import { BaseCommand } from '../../base-command';
2
+ /** A single read of the three owner-only Security Center surfaces. */
3
+ export default class SecurityOverview extends BaseCommand {
4
+ static description: string;
5
+ static examples: string[];
6
+ static flags: {
7
+ limit: import("@oclif/core/lib/interfaces").OptionFlag<number | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
8
+ json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
9
+ };
10
+ run(): Promise<void>;
11
+ }
@@ -0,0 +1,158 @@
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 api_fetch_1 = require("../../lib/api-fetch");
6
+ function rows(value, name) {
7
+ if (!Array.isArray(value))
8
+ throw new Error(`Security overview: invalid ${name} response`);
9
+ if (value.some((row) => row === null || typeof row !== 'object' || Array.isArray(row))) {
10
+ throw new Error(`Security overview: invalid ${name} row`);
11
+ }
12
+ return value;
13
+ }
14
+ function hasMore(value, name) {
15
+ if (typeof value !== 'boolean') {
16
+ throw new Error(`Security overview: invalid ${name} pagination`);
17
+ }
18
+ return value;
19
+ }
20
+ function activeTokens(value) {
21
+ const tokens = rows(value, 'access tokens');
22
+ // A missing or unfamiliar state must not turn an active credential into a
23
+ // reassuring "None". Validate the whole list before filtering it.
24
+ if (tokens.some((row) => typeof row.state !== 'string' || !['active', 'expired', 'revoked'].includes(row.state))) {
25
+ throw new Error('Security overview: invalid access token state');
26
+ }
27
+ return tokens.filter((row) => row.state === 'active');
28
+ }
29
+ function string(value) {
30
+ return typeof value === 'string' ? value : null;
31
+ }
32
+ /** Explicit allow-lists: the sessions API also returns a full IP address. */
33
+ function safeSession(row) {
34
+ return {
35
+ id: string(row.id) ?? '',
36
+ kind: string(row.kind) ?? 'unknown',
37
+ device: string(row.device) ?? 'Unknown device',
38
+ label: string(row.label),
39
+ daemonId: string(row.daemonId),
40
+ network: string(row.network),
41
+ country: string(row.country),
42
+ createdAt: string(row.createdAt),
43
+ lastActiveAt: string(row.lastActiveAt),
44
+ current: row.current === true,
45
+ };
46
+ }
47
+ function safeActivity(row) {
48
+ const device = row.device && typeof row.device === 'object' && !Array.isArray(row.device)
49
+ ? row.device
50
+ : {};
51
+ const session = row.session && typeof row.session === 'object' && !Array.isArray(row.session)
52
+ ? row.session
53
+ : {};
54
+ return {
55
+ id: string(row.id) ?? '',
56
+ action: string(row.action) ?? 'unknown',
57
+ outcome: string(row.outcome) ?? 'unknown',
58
+ method: string(row.method),
59
+ device: string(device.label),
60
+ network: string(row.network),
61
+ country: string(row.country),
62
+ newDevice: row.newDevice === true,
63
+ reportedNotMe: row.reportedNotMe === true,
64
+ createdAt: string(row.createdAt) ?? '',
65
+ sessionId: string(session.id),
66
+ };
67
+ }
68
+ function safeToken(row) {
69
+ return {
70
+ id: string(row.id) ?? '',
71
+ name: string(row.name) ?? '',
72
+ lastFour: string(row.lastFour) ?? '',
73
+ scopes: Array.isArray(row.scopes)
74
+ ? row.scopes.filter((v) => typeof v === 'string')
75
+ : [],
76
+ expiresAt: string(row.expiresAt),
77
+ lastUsedAt: string(row.lastUsedAt),
78
+ };
79
+ }
80
+ /** A single read of the three owner-only Security Center surfaces. */
81
+ class SecurityOverview extends base_command_1.BaseCommand {
82
+ static description = 'Review active sessions, recent security activity, and access tokens';
83
+ static examples = [
84
+ '<%= config.bin %> security overview',
85
+ '<%= config.bin %> security overview --limit 50 --json',
86
+ ];
87
+ static flags = {
88
+ limit: core_1.Flags.integer({
89
+ description: 'Sessions and events to show (default 25, maximum 100)',
90
+ min: 1,
91
+ max: 100,
92
+ }),
93
+ json: core_1.Flags.boolean({ description: 'Output JSON' }),
94
+ };
95
+ async run() {
96
+ this.requireAuth();
97
+ const { flags } = await this.parse(SecurityOverview);
98
+ const limit = flags.limit ?? 25;
99
+ let sessionPage;
100
+ let activityPage;
101
+ let tokenPage;
102
+ try {
103
+ // All three reads must succeed. A partial response could claim an
104
+ // account has no sessions or tokens when a Security Center read failed.
105
+ [sessionPage, activityPage, tokenPage] = await Promise.all([
106
+ (0, api_fetch_1.apiFetch)(`/api/auth/sessions/refresh?limit=${limit}`, {
107
+ credential: this.resolvedCredential,
108
+ }),
109
+ (0, api_fetch_1.apiFetch)(`/api/user/security-activity?limit=${limit}`, {
110
+ credential: this.resolvedCredential,
111
+ }),
112
+ (0, api_fetch_1.apiFetch)('/api/user/access-tokens', {
113
+ credential: this.resolvedCredential,
114
+ }),
115
+ ]);
116
+ const sessions = rows(sessionPage?.sessions, 'sessions').map(safeSession);
117
+ const activity = rows(activityPage?.events, 'activity').map(safeActivity);
118
+ const accessTokens = activeTokens(tokenPage?.accessTokens).map(safeToken);
119
+ const overview = {
120
+ sessions,
121
+ sessionsHaveMore: hasMore(sessionPage?.hasMore, 'sessions'),
122
+ activity,
123
+ activityHasMore: hasMore(activityPage?.hasMore, 'activity'),
124
+ accessTokens,
125
+ };
126
+ if (flags.json) {
127
+ this.log(JSON.stringify(overview, null, 2));
128
+ return;
129
+ }
130
+ this.log(`Active sessions (${sessions.length}${overview.sessionsHaveMore ? '+' : ''}):`);
131
+ for (const s of sessions) {
132
+ this.log(` ${s.id} ${s.current ? '[this session] ' : ''}${s.label || s.device} ${s.kind} ${s.country || s.network || 'unknown location'} last active ${s.lastActiveAt || 'unknown'}`);
133
+ }
134
+ if (!sessions.length)
135
+ this.log(' None.');
136
+ this.log(`Recent security activity (${activity.length}${overview.activityHasMore ? '+' : ''}):`);
137
+ for (const event of activity) {
138
+ this.log(` ${event.createdAt} ${event.action} (${event.outcome}) ${event.device || 'unknown device'} ${event.country || event.network || 'unknown location'}${event.newDevice ? ' NEW DEVICE' : ''}${event.reportedNotMe ? ' REPORTED' : ''} ${event.id}`);
139
+ }
140
+ if (!activity.length)
141
+ this.log(' None.');
142
+ this.log(`Active access tokens (${accessTokens.length}):`);
143
+ for (const token of accessTokens) {
144
+ this.log(` ${token.id} ${token.name} …${token.lastFour} expires ${token.expiresAt || 'unknown'}`);
145
+ }
146
+ if (!accessTokens.length)
147
+ this.log(' None.');
148
+ if (overview.sessionsHaveMore || overview.activityHasMore) {
149
+ this.log('More sessions or events are available in Account settings; increase --limit to see up to 100 here.');
150
+ }
151
+ this.log('To review an unfamiliar sign-in and secure your account, open Account settings > Security activity.');
152
+ }
153
+ catch (err) {
154
+ this.handleApiError(err);
155
+ }
156
+ }
157
+ }
158
+ exports.default = SecurityOverview;
@@ -0,0 +1,12 @@
1
+ import { BaseCommand } from '../../base-command';
2
+ export default class SpacesClaim extends BaseCommand {
3
+ static description: string;
4
+ static examples: string[];
5
+ static args: {
6
+ id: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
7
+ };
8
+ static flags: {
9
+ json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
10
+ };
11
+ run(): Promise<void>;
12
+ }
@@ -0,0 +1,42 @@
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
+ const web_url_1 = require("../../lib/web-url");
7
+ class SpacesClaim extends base_command_1.BaseCommand {
8
+ static description = 'Claim an ownerless space when you are eligible';
9
+ static examples = [
10
+ '<%= config.bin %> spaces claim <space-id>',
11
+ '<%= config.bin %> spaces claim "Team workspace" --json',
12
+ ];
13
+ static args = {
14
+ id: core_1.Args.string({
15
+ description: 'Space ID, identifier, or title',
16
+ required: true,
17
+ ignoreStdin: true,
18
+ }),
19
+ };
20
+ static flags = {
21
+ json: core_1.Flags.boolean({ description: 'Output the claim result as JSON' }),
22
+ };
23
+ async run() {
24
+ this.requireAuth();
25
+ const { args, flags } = await this.parse(SpacesClaim);
26
+ const spaceId = await this.resolveSpaceReference(args.id);
27
+ let result;
28
+ try {
29
+ result = await data_provider_1.dataService.claimSpaceOwnership(spaceId);
30
+ }
31
+ catch (err) {
32
+ this.handleApiError(err);
33
+ }
34
+ const url = (0, web_url_1.spaceUrlFrom)(this.cliConfig.baseURL, { id: result.spaceId });
35
+ if (flags.json) {
36
+ this.log(JSON.stringify({ ...result, ...(url ? { url } : {}) }, null, 2));
37
+ return;
38
+ }
39
+ this.log(`Claimed ownership of space ${result.spaceId}${url ? `: ${url}` : ''}`);
40
+ }
41
+ }
42
+ exports.default = SpacesClaim;
@@ -0,0 +1,12 @@
1
+ import { BaseCommand } from '../../base-command';
2
+ export default class SpacesOwnership extends BaseCommand {
3
+ static description: string;
4
+ static examples: string[];
5
+ static args: {
6
+ id: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
7
+ };
8
+ static flags: {
9
+ json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
10
+ };
11
+ run(): Promise<void>;
12
+ }
@@ -0,0 +1,46 @@
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
+ class SpacesOwnership extends base_command_1.BaseCommand {
7
+ static description = 'Show whether a space is ownerless and whether you may claim it';
8
+ static examples = [
9
+ '<%= config.bin %> spaces ownership <space-id>',
10
+ '<%= config.bin %> spaces ownership OSK-S-12 --json',
11
+ ];
12
+ static args = {
13
+ id: core_1.Args.string({
14
+ description: 'Space ID, identifier, or title',
15
+ required: true,
16
+ ignoreStdin: true,
17
+ }),
18
+ };
19
+ static flags = {
20
+ json: core_1.Flags.boolean({ description: 'Output the ownership state as JSON' }),
21
+ };
22
+ async run() {
23
+ this.requireAuth();
24
+ const { args, flags } = await this.parse(SpacesOwnership);
25
+ const spaceId = await this.resolveSpaceReference(args.id);
26
+ let ownership;
27
+ try {
28
+ ownership = await data_provider_1.dataService.getSpaceOwnership(spaceId);
29
+ }
30
+ catch (err) {
31
+ this.handleApiError(err);
32
+ }
33
+ if (flags.json) {
34
+ this.log(JSON.stringify(ownership, null, 2));
35
+ return;
36
+ }
37
+ if (!ownership.ownerless) {
38
+ this.log('This space has an owner.');
39
+ return;
40
+ }
41
+ this.log(ownership.canClaim
42
+ ? `This space is ownerless. You may claim it with skrr spaces claim ${spaceId}.`
43
+ : `This space is ownerless. You cannot claim it yet (${ownership.reason}).`);
44
+ }
45
+ }
46
+ exports.default = SpacesOwnership;
@@ -5,7 +5,7 @@ const data_provider_1 = require("@skrr-ai/data-provider");
5
5
  const base_command_1 = require("../../base-command");
6
6
  const prompt_1 = require("../../lib/prompt");
7
7
  class TasksCancelCascade extends base_command_1.BaseCommand {
8
- static description = 'Cancel an active cascade pipeline for a task';
8
+ static description = 'Cancel running and queued work in the active cascade; future schedules stay enabled';
9
9
  static examples = [
10
10
  '<%= config.bin %> tasks cancel-cascade <task-id>',
11
11
  '<%= config.bin %> tasks cancel-cascade <task-id> --yes',
@@ -52,9 +52,13 @@ export declare function partialFailureResult(args: {
52
52
  failedAt: TreeFailure;
53
53
  total: number;
54
54
  prefix?: string;
55
+ keyed?: boolean;
55
56
  warnings: Array<Record<string, unknown>>;
57
+ failures?: Array<Record<string, unknown>>;
56
58
  }): {
57
59
  warnings?: Record<string, unknown>[] | undefined;
60
+ rerun: string;
61
+ failures?: Record<string, unknown>[] | undefined;
58
62
  object: string;
59
63
  ok: boolean;
60
64
  count: number;
@@ -69,7 +73,6 @@ export declare function partialFailureResult(args: {
69
73
  }[];
70
74
  mapping: Record<string, string>;
71
75
  failedAt: TreeFailure;
72
- rerun: string;
73
76
  };
74
77
  export default class TasksCreateTree extends BaseCommand {
75
78
  static description: string;
@@ -80,6 +83,7 @@ export default class TasksCreateTree extends BaseCommand {
80
83
  'dry-run': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
81
84
  'external-key-prefix': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
82
85
  fresh: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
86
+ resume: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
83
87
  'default-agent': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
84
88
  space: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
85
89
  goal: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
@@ -78,8 +78,9 @@ function partialFailureResult(args) {
78
78
  })),
79
79
  mapping,
80
80
  failedAt,
81
- rerun: prefix
82
- ? 'Safe to re-run the same command: nodes with an externalKey are updated, not duplicated.'
81
+ ...(args.failures ? { failures: args.failures } : {}),
82
+ rerun: prefix || args.keyed
83
+ ? 'Re-run the same keyed file with --resume to confirm existing IDs and create only missing nodes. A submitted write may still finish; stable keys prevent duplicates.'
83
84
  : 'Re-running creates un-keyed nodes again. Use mapping to continue by hand, or re-run with --external-key-prefix.',
84
85
  ...(warnings.length > 0 ? { warnings } : {}),
85
86
  };
@@ -105,6 +106,10 @@ class TasksCreateTree extends base_command_1.BaseCommand {
105
106
  fresh: core_1.Flags.boolean({
106
107
  description: 'Do not derive a key prefix from the file: create a new copy even if this exact file was applied before',
107
108
  }),
109
+ resume: core_1.Flags.boolean({
110
+ description: 'Confirm existing stable-key nodes without patching them, then create only missing nodes. Use the unchanged keyed file after an incomplete result.',
111
+ exclusive: ['fresh'],
112
+ }),
108
113
  'default-agent': core_1.Flags.string({
109
114
  description: 'Override the automatic assignee for nodes with no assignee. Use "auto" to inherit the space lead agent; children inherit parent assignees first.',
110
115
  }),
@@ -212,6 +217,8 @@ class TasksCreateTree extends base_command_1.BaseCommand {
212
217
  }
213
218
  const errors = [
214
219
  ...defaultAgentErrors,
220
+ ...(flags.resume && unkeyedNodes(planned).length
221
+ ? ['--resume requires an externalKey for every node; supply --external-key-prefix and do not use --fresh'] : []),
215
222
  ...planned.flatMap(({ key, payload }) => (0, tasks_1.validateTaskCreatePayload)(payload).map((error) => `${key}: ${error}`)),
216
223
  ];
217
224
  if (errors.length > 0) {
@@ -267,15 +274,17 @@ class TasksCreateTree extends base_command_1.BaseCommand {
267
274
  const mapping = {};
268
275
  const results = [];
269
276
  // A half-applied tree reports the ids it did create; the only way back.
270
- const reportPartial = (item, failure, err) => {
271
- if (results.length === 0)
277
+ const reportPartial = (item, failure, err, failures) => {
278
+ if (results.length === 0 && !failures)
272
279
  this.handleApiError(err);
273
280
  const partial = partialFailureResult({
274
281
  results,
275
282
  mapping,
276
283
  total: planned.length,
277
284
  prefix: keyPrefix,
285
+ keyed: unkeyedNodes(planned).length === 0,
278
286
  warnings,
287
+ failures,
279
288
  failedAt: {
280
289
  tempId: item.key,
281
290
  ...(item.parentKey ? { parentTempId: item.parentKey } : {}),
@@ -288,10 +297,18 @@ class TasksCreateTree extends base_command_1.BaseCommand {
288
297
  process.exitCode = partial.failedAt.error.retryable ? 5 : 1;
289
298
  this.exit(partial.failedAt.error.retryable ? 5 : 1);
290
299
  }
291
- this.log(`Created ${results.length} of ${planned.length} task(s) before "${item.key}" failed:`);
300
+ this.log(`Confirmed ${results.length} of ${planned.length} task(s); stopped at "${item.key}":`);
292
301
  for (const [tempId, id] of Object.entries(mapping))
293
302
  this.log(`- ${tempId} -> ${id}`);
294
303
  this.log(partial.rerun);
304
+ if (failures) {
305
+ for (const row of failures) {
306
+ const detail = row.error;
307
+ this.log(`- ${String(row.tempId)}: ${String(row.outcome)} — ${detail.message}`);
308
+ }
309
+ this.exit(failure.retryable ? 5 : 1);
310
+ return;
311
+ }
295
312
  this.handleApiError(err);
296
313
  };
297
314
  // The serial path: one request per node. Used for updates of existing keyed
@@ -381,7 +398,11 @@ class TasksCreateTree extends base_command_1.BaseCommand {
381
398
  ...(failure.error.status ? { status: failure.error.status } : {}),
382
399
  ...(failure.error.code ? { code: failure.error.code } : {}),
383
400
  });
384
- reportPartial(node, failure.error, err);
401
+ reportPartial(node, failure.error, err, outcome.failures.map((f) => ({
402
+ tempId: f.key, outcome: f.outcome ?? (f.skipped ? 'not_attempted' : 'failed'),
403
+ externalKey: f.externalKey ?? byKey.get(f.key)?.payload.externalKey,
404
+ error: f.error,
405
+ })));
385
406
  }
386
407
  return;
387
408
  }
@@ -395,7 +416,14 @@ class TasksCreateTree extends base_command_1.BaseCommand {
395
416
  if (existingTask) {
396
417
  // An update must see its parent's id, and a later create its own parent's.
397
418
  await flushPending();
398
- await applySerial(item);
419
+ if (flags.resume) {
420
+ const id = (0, tasks_1.taskId)(existingTask);
421
+ mapping[item.key] = id;
422
+ results.push({ tempId: item.key, parentTempId: item.parentKey, id, action: 'existing',
423
+ externalKey: item.payload.externalKey, title: existingTask.title ?? item.payload.title, task: existingTask });
424
+ }
425
+ else
426
+ await applySerial(item);
399
427
  }
400
428
  else {
401
429
  pending.push(item);
@@ -10,7 +10,7 @@ const base_command_1 = require("../../base-command");
10
10
  * activity log shows who pulled the plug and why.
11
11
  */
12
12
  class TasksInterrupt extends base_command_1.BaseCommand {
13
- static description = 'Interrupt a running task (posts an interrupt-flagged comment that the agent runtime honors)';
13
+ static description = 'Request an interrupt for the current run; child runs, queued work and future schedules are unchanged';
14
14
  static examples = [
15
15
  '<%= config.bin %> tasks interrupt <task-id>',
16
16
  '<%= config.bin %> tasks interrupt <task-id> --reason "wrong file"',
@@ -45,7 +45,7 @@ class TasksInterrupt extends base_command_1.BaseCommand {
45
45
  return;
46
46
  }
47
47
  const c = result;
48
- this.log(`Interrupt sent to task ${taskRef} (comment ${c.id ?? c._id ?? '?'})`);
48
+ this.log(`Stop requested for task ${taskRef} (comment ${c.id ?? c._id ?? '?'}). Child runs and schedules are unchanged; inspect run state to confirm it stopped.`);
49
49
  }
50
50
  }
51
51
  exports.default = TasksInterrupt;
@@ -5,7 +5,7 @@ const data_provider_1 = require("@skrr-ai/data-provider");
5
5
  const base_command_1 = require("../../base-command");
6
6
  const web_url_1 = require("../../lib/web-url");
7
7
  class TasksPauseSchedule extends base_command_1.BaseCommand {
8
- static description = 'Pause a recurring task — keeps the recurrence definition but stops future fires';
8
+ static description = 'Pause future recurring runs; current runs and already queued work are unchanged';
9
9
  static examples = [
10
10
  '<%= config.bin %> tasks pause-schedule <task-id>',
11
11
  '<%= config.bin %> tasks pause-schedule <task-id> --json',
@@ -12,11 +12,9 @@ import { BaseCommand } from '../../base-command';
12
12
  * `GET /api/auth/sessions/refresh` has served exactly this list all along; it
13
13
  * was simply not reachable from the CLI.
14
14
  *
15
- * The server returns at most `--limit` rows and says whether it left any out.
16
- * It used to stop at 100 without saying so, and the session somebody went
17
- * looking for — an old daemon's, still holding the login a purge refusal named
18
- * — was exactly the one past the cut (OSK-9980). So a partial list says it is
19
- * partial, and `--kind daemon` is the short way to the row that matters.
15
+ * The server returns at most 500 rows per page; this command follows cursors
16
+ * until its total `--limit` is reached. The default stays at 100. Older servers
17
+ * without cursors can only return their first page, which is disclosed.
20
18
  */
21
19
  export default class TokenList extends BaseCommand {
22
20
  static description: string;