@skrr-ai/cli 0.1.94 → 0.1.96
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.
- package/README.md +175 -165
- package/bin/run.js +32 -0
- package/bin/update-npm-runner.cjs +33 -0
- package/bin/update-session.cjs +194 -0
- package/bin/update-worker.cjs +208 -0
- package/dist/base-command.js +0 -15
- package/dist/commands/agent/message/send.d.ts +2 -0
- package/dist/commands/agent/message/send.js +75 -5
- package/dist/commands/agents/chat.js +1 -1
- package/dist/commands/agents/create.d.ts +0 -3
- package/dist/commands/agents/create.js +0 -26
- package/dist/commands/agents/egress.d.ts +17 -0
- package/dist/commands/agents/egress.js +84 -0
- package/dist/commands/agents/mailbox/messages.d.ts +14 -0
- package/dist/commands/agents/mailbox/messages.js +52 -0
- package/dist/commands/agents/mailbox/show.d.ts +13 -0
- package/dist/commands/agents/mailbox/show.js +52 -0
- package/dist/commands/agents/operating-plan.js +8 -0
- package/dist/commands/agents/roles/capture.d.ts +16 -0
- package/dist/commands/agents/roles/capture.js +53 -0
- package/dist/commands/agents/roles/install.d.ts +1 -0
- package/dist/commands/agents/roles/install.js +8 -1
- package/dist/commands/agents/roles/preview.d.ts +2 -0
- package/dist/commands/agents/roles/preview.js +27 -3
- package/dist/commands/agents/update.d.ts +0 -3
- package/dist/commands/agents/update.js +1 -31
- package/dist/commands/agents/usage-cap.d.ts +4 -1
- package/dist/commands/agents/usage-cap.js +49 -10
- package/dist/commands/code/doctor.d.ts +1 -0
- package/dist/commands/code/doctor.js +6 -0
- package/dist/commands/code/index.js +4 -0
- package/dist/commands/code/install.d.ts +5 -62
- package/dist/commands/code/install.js +86 -279
- package/dist/commands/code/jobs/show.js +12 -0
- package/dist/commands/code/rollback.d.ts +8 -0
- package/dist/commands/code/rollback.js +20 -0
- package/dist/commands/code/update.d.ts +8 -37
- package/dist/commands/code/update.js +134 -213
- package/dist/commands/commitments/action-proposals/decide.d.ts +2 -0
- package/dist/commands/commitments/action-proposals/decide.js +12 -0
- package/dist/commands/commitments/action-proposals.js +7 -0
- package/dist/commands/commitments/create.d.ts +1 -0
- package/dist/commands/commitments/create.js +17 -1
- package/dist/commands/commitments/delivery.d.ts +1 -0
- package/dist/commands/commitments/delivery.js +11 -1
- package/dist/commands/commitments/doctor.js +4 -0
- package/dist/commands/commitments/effective-policy.js +26 -0
- package/dist/commands/commitments/endpoints/create.js +5 -2
- package/dist/commands/commitments/observations.d.ts +10 -0
- package/dist/commands/commitments/observations.js +88 -0
- package/dist/commands/commitments/pause.d.ts +1 -1
- package/dist/commands/commitments/pause.js +4 -3
- package/dist/commands/commitments/preflight.js +2 -1
- package/dist/commands/commitments/reports.d.ts +2 -0
- package/dist/commands/commitments/reports.js +22 -4
- package/dist/commands/commitments/update.d.ts +1 -0
- package/dist/commands/commitments/update.js +35 -5
- package/dist/commands/daemon/install.js +4 -0
- package/dist/commands/generate-recovery-code.d.ts +13 -1
- package/dist/commands/generate-recovery-code.js +146 -13
- package/dist/commands/grants/create.d.ts +1 -0
- package/dist/commands/grants/create.js +8 -0
- package/dist/commands/grants/rotate.d.ts +5 -0
- package/dist/commands/grants/rotate.js +13 -1
- package/dist/commands/grants/update.d.ts +12 -0
- package/dist/commands/grants/update.js +27 -0
- package/dist/commands/instructions/guide.d.ts +20 -0
- package/dist/commands/instructions/guide.js +84 -0
- package/dist/commands/instructions/install.js +57 -33
- package/dist/commands/instructions/setup.d.ts +14 -0
- package/dist/commands/instructions/setup.js +72 -0
- package/dist/commands/integrations/execute.js +1 -1
- package/dist/commands/login.d.ts +5 -6
- package/dist/commands/login.js +27 -11
- package/dist/commands/logout.js +7 -7
- package/dist/commands/machines/hosted/start.js +7 -1
- package/dist/commands/machines/list.js +4 -4
- package/dist/commands/measures/confirm.d.ts +1 -7
- package/dist/commands/measures/confirm.js +15 -14
- package/dist/commands/measures/list.d.ts +1 -7
- package/dist/commands/measures/list.js +1 -7
- package/dist/commands/measures/record.js +4 -4
- package/dist/commands/measures/show.d.ts +11 -0
- package/dist/commands/measures/show.js +26 -0
- package/dist/commands/northstars/governance/configure.d.ts +1 -0
- package/dist/commands/northstars/governance/configure.js +8 -1
- package/dist/commands/proposals/discard.d.ts +5 -0
- package/dist/commands/proposals/discard.js +8 -0
- package/dist/commands/proposals/keep.d.ts +5 -0
- package/dist/commands/proposals/keep.js +8 -0
- package/dist/commands/proposals/list.d.ts +15 -0
- package/dist/commands/proposals/list.js +74 -0
- package/dist/commands/proposals/show.d.ts +12 -0
- package/dist/commands/proposals/show.js +27 -0
- package/dist/commands/proposals/snooze.d.ts +5 -0
- package/dist/commands/proposals/snooze.js +8 -0
- package/dist/commands/security/overview.d.ts +11 -0
- package/dist/commands/security/overview.js +158 -0
- package/dist/commands/signals/monitors/create.d.ts +9 -0
- package/dist/commands/signals/monitors/create.js +29 -0
- package/dist/commands/signals/monitors/list.d.ts +8 -0
- package/dist/commands/signals/monitors/list.js +16 -0
- package/dist/commands/signals/monitors/pause.d.ts +11 -0
- package/dist/commands/signals/monitors/pause.js +17 -0
- package/dist/commands/signals/monitors/resume.d.ts +11 -0
- package/dist/commands/signals/monitors/resume.js +17 -0
- package/dist/commands/signals/monitors/revise.d.ts +12 -0
- package/dist/commands/signals/monitors/revise.js +25 -0
- package/dist/commands/signals/monitors/show.d.ts +11 -0
- package/dist/commands/signals/monitors/show.js +17 -0
- package/dist/commands/signals/monitors/test.d.ts +12 -0
- package/dist/commands/signals/monitors/test.js +25 -0
- package/dist/commands/signals/monitors/transitions.d.ts +11 -0
- package/dist/commands/signals/monitors/transitions.js +17 -0
- package/dist/commands/signals/monitors/validate.d.ts +9 -0
- package/dist/commands/signals/monitors/validate.js +24 -0
- package/dist/commands/signals/schemas/list.d.ts +8 -0
- package/dist/commands/signals/schemas/list.js +16 -0
- package/dist/commands/signals/schemas/show.d.ts +11 -0
- package/dist/commands/signals/schemas/show.js +17 -0
- package/dist/commands/signals/sources/list.d.ts +8 -0
- package/dist/commands/signals/sources/list.js +16 -0
- package/dist/commands/signals/sources/receipts.d.ts +11 -0
- package/dist/commands/signals/sources/receipts.js +17 -0
- package/dist/commands/signals/sources/show.d.ts +11 -0
- package/dist/commands/signals/sources/show.js +17 -0
- package/dist/commands/spaces/claim.d.ts +12 -0
- package/dist/commands/spaces/claim.js +42 -0
- package/dist/commands/spaces/ownership.d.ts +12 -0
- package/dist/commands/spaces/ownership.js +46 -0
- package/dist/commands/spaces/usage.d.ts +12 -0
- package/dist/commands/spaces/usage.js +28 -0
- package/dist/commands/spaces/workflows/run.js +3 -0
- package/dist/commands/spaces/workflows/runs.js +2 -1
- package/dist/commands/spaces/workflows/update.d.ts +6 -1
- package/dist/commands/spaces/workflows/update.js +34 -2
- package/dist/commands/tasks/cancel-cascade.js +1 -1
- package/dist/commands/tasks/cascade-status.d.ts +2 -0
- package/dist/commands/tasks/cascade-status.js +27 -1
- package/dist/commands/tasks/create-tree.d.ts +5 -1
- package/dist/commands/tasks/create-tree.js +35 -7
- package/dist/commands/tasks/create.d.ts +4 -0
- package/dist/commands/tasks/create.js +16 -0
- package/dist/commands/tasks/deliverable/add.js +12 -3
- package/dist/commands/tasks/economics-compare.d.ts +15 -0
- package/dist/commands/tasks/economics-compare.js +60 -0
- package/dist/commands/tasks/expectations/evaluate.d.ts +13 -0
- package/dist/commands/tasks/expectations/evaluate.js +43 -0
- package/dist/commands/tasks/interrupt.js +2 -2
- package/dist/commands/tasks/list.d.ts +1 -0
- package/dist/commands/tasks/list.js +44 -1
- package/dist/commands/tasks/pause-schedule.js +1 -1
- package/dist/commands/tasks/show.d.ts +5 -0
- package/dist/commands/tasks/show.js +17 -0
- package/dist/commands/tasks/update.d.ts +4 -0
- package/dist/commands/tasks/update.js +9 -0
- package/dist/commands/tasks/workflow-findings.js +2 -2
- package/dist/commands/token/list.d.ts +3 -5
- package/dist/commands/token/list.js +99 -57
- package/dist/commands/triggers/create.d.ts +2 -0
- package/dist/commands/triggers/create.js +15 -0
- package/dist/commands/update.d.ts +14 -0
- package/dist/commands/update.js +61 -0
- package/dist/commands/workspaces/grants.d.ts +12 -0
- package/dist/commands/workspaces/grants.js +31 -0
- package/dist/commands/workspaces/stewardship.d.ts +13 -0
- package/dist/commands/workspaces/stewardship.js +34 -0
- package/dist/generated/managed-instruction-guides.json +9 -0
- package/dist/hooks/init.d.ts +5 -0
- package/dist/hooks/init.js +27 -0
- package/dist/lib/agent-config.d.ts +0 -12
- package/dist/lib/agent-config.js +1 -27
- package/dist/lib/agent-mailbox.d.ts +35 -0
- package/dist/lib/agent-mailbox.js +55 -0
- package/dist/lib/commitment-endpoints.d.ts +2 -1
- package/dist/lib/commitment-endpoints.js +2 -5
- package/dist/lib/commitment-product.d.ts +3 -1
- package/dist/lib/commitment-product.js +10 -0
- package/dist/lib/commitment-terms.js +1 -1
- package/dist/lib/commitments.d.ts +17 -15
- package/dist/lib/commitments.js +173 -137
- package/dist/lib/compasses.d.ts +14 -0
- package/dist/lib/compasses.js +25 -11
- package/dist/lib/daemon-installer.d.ts +2 -0
- package/dist/lib/daemon-installer.js +100 -24
- package/dist/lib/first-party-harness-doctor.js +31 -1
- package/dist/lib/first-party-harness-remote-install.d.ts +71 -0
- package/dist/lib/first-party-harness-remote-install.js +310 -0
- package/dist/lib/first-party-harness-remote-update.d.ts +46 -0
- package/dist/lib/first-party-harness-remote-update.js +242 -0
- package/dist/lib/first-party-harness-startup-update.d.ts +26 -0
- package/dist/lib/first-party-harness-startup-update.js +166 -0
- package/dist/lib/first-party-harness-update.d.ts +46 -0
- package/dist/lib/first-party-harness-update.js +289 -0
- package/dist/lib/first-party-harness.js +7 -4
- package/dist/lib/grants.d.ts +5 -1
- package/dist/lib/grants.js +9 -1
- package/dist/lib/hosted-machines.d.ts +7 -0
- package/dist/lib/mailbox-human-command.d.ts +11 -0
- package/dist/lib/mailbox-human-command.js +41 -0
- package/dist/lib/managed-instruction-local.d.ts +12 -0
- package/dist/lib/managed-instruction-local.js +58 -0
- package/dist/lib/managed-instruction-setup.d.ts +35 -0
- package/dist/lib/managed-instruction-setup.js +306 -0
- package/dist/lib/measures.d.ts +28 -2
- package/dist/lib/measures.js +38 -14
- package/dist/lib/memory-sync.d.ts +1 -0
- package/dist/lib/memory-sync.js +279 -68
- package/dist/lib/message-intent.d.ts +6 -2
- package/dist/lib/message-intent.js +1 -0
- package/dist/lib/northstars.js +2 -1
- package/dist/lib/platform-update-local.d.ts +2 -0
- package/dist/lib/platform-update-local.js +113 -0
- package/dist/lib/platform-update.d.ts +39 -0
- package/dist/lib/platform-update.js +84 -0
- package/dist/lib/profile-machine-identity-guard.d.ts +2 -0
- package/dist/lib/profile-machine-identity-guard.js +13 -0
- package/dist/lib/prompt.d.ts +1 -0
- package/dist/lib/prompt.js +4 -3
- package/dist/lib/proposal-decision-command.d.ts +19 -0
- package/dist/lib/proposal-decision-command.js +64 -0
- package/dist/lib/proposals.d.ts +8 -0
- package/dist/lib/proposals.js +22 -0
- package/dist/lib/roles.d.ts +141 -6
- package/dist/lib/roles.js +8 -2
- package/dist/lib/signals.d.ts +20 -0
- package/dist/lib/signals.js +24 -0
- package/dist/lib/space-usage.d.ts +3 -0
- package/dist/lib/space-usage.js +17 -0
- package/dist/lib/stewardship-output.d.ts +4 -0
- package/dist/lib/stewardship-output.js +82 -0
- package/dist/lib/task-asset-upload.d.ts +3 -1
- package/dist/lib/task-asset-upload.js +1 -0
- package/dist/lib/task-batch.d.ts +9 -0
- package/dist/lib/task-batch.js +94 -17
- package/dist/lib/task-economics.d.ts +16 -0
- package/dist/lib/task-economics.js +22 -0
- package/dist/lib/task-instruction-offer.d.ts +1 -4
- package/dist/lib/task-instruction-offer.js +3 -129
- package/dist/lib/triggers.d.ts +11 -3
- package/dist/lib/triggers.js +58 -1
- package/dist/lib/update-check.d.ts +61 -53
- package/dist/lib/update-check.js +453 -314
- package/dist/lib/usage-discovery.d.ts +10 -0
- package/dist/lib/usage-discovery.js +24 -3
- package/dist/lib/workflow-run-budget.d.ts +12 -0
- package/dist/lib/workflow-run-budget.js +37 -0
- package/dist/lib/workspaces.d.ts +48 -0
- package/dist/lib/workspaces.js +8 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/cliHandoffWire.d.ts +4 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/cliHandoffWire.js +14 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/firstPartyHarnessChannels.d.ts +3 -4
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/firstPartyHarnessChannels.js +3 -4
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/firstPartyHarnessReleaseKeys.d.ts +82 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/firstPartyHarnessReleaseKeys.js +105 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/firstPartyHarnessUpdater.d.ts +110 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/firstPartyHarnessUpdater.js +945 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/githubPushRefusal.d.ts +9 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/githubPushRefusal.js +49 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/harnessPermissionControls.d.ts +32 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/harnessPermissionControls.js +62 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.d.ts +2 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.js +4 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/machineUuid.js +35 -5
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/releasePolicy.d.ts +63 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/releasePolicy.js +169 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/releaseVersions.d.ts +2 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/releaseVersions.js +44 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/cliHandoffWire.d.ts +4 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/cliHandoffWire.js +12 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/firstPartyHarnessChannels.d.ts +3 -4
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/firstPartyHarnessChannels.js +3 -4
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/firstPartyHarnessReleaseKeys.d.ts +82 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/firstPartyHarnessReleaseKeys.js +102 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/firstPartyHarnessUpdater.d.ts +110 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/firstPartyHarnessUpdater.js +923 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/githubPushRefusal.d.ts +9 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/githubPushRefusal.js +44 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/harnessPermissionControls.d.ts +32 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/harnessPermissionControls.js +59 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.d.ts +2 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.js +2 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/machineUuid.js +35 -5
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/releasePolicy.d.ts +63 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/releasePolicy.js +163 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/releaseVersions.d.ts +2 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/releaseVersions.js +41 -0
- package/dist/node_modules/@skrr-ai/auth-core/package.json +41 -1
- package/dist/node_modules/@skrr-ai/data-provider/index.js +8069 -7121
- package/docs/v1/README.md +28 -0
- package/docs/v1/api.md +55 -0
- package/docs/v1/changelog.md +16 -0
- package/docs/v1/check-examples.mjs +178 -0
- package/docs/v1/examples/api-readonly.mjs +144 -0
- package/docs/v1/operations.md +87 -0
- package/docs/v1/quickstart.md +71 -0
- package/oclif.manifest.json +49424 -46174
- package/package.json +34 -11
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# skrr developer guide — v1
|
|
2
|
+
|
|
3
|
+
Build a useful first integration, understand its authority and cost, and recover when a request fails.
|
|
4
|
+
|
|
5
|
+
This edition was reviewed on **2026-10-10** against CLI **0.1.95**, daemon **0.8.116**, and the public `/v1` source contract. These are source versions, not release attestations. Check your installed CLI with `skrr --version` and its schemas with `skrr commands --schema "<command>"`. Install the CLI from the public npm package; this guide and its runnable example are included in that package and require no private repository access.
|
|
6
|
+
|
|
7
|
+
| Your goal | Start here |
|
|
8
|
+
| --- | --- |
|
|
9
|
+
| Sign in and create your first Task | [Quickstart](quickstart.md) |
|
|
10
|
+
| Make a read-only HTTP request with a scoped key | [Public API](api.md) |
|
|
11
|
+
| Fix sign-in, errors, quotas, permissions, or a failed upgrade | [Operations and recovery](operations.md) |
|
|
12
|
+
| Understand what changed in this guide | [Changelog](changelog.md) |
|
|
13
|
+
|
|
14
|
+
The workspace CLI uses the account/workspace `/api` surface. The Managed Agents public API uses `/v1` and Space-scoped API keys. A personal access token for CLI automation is a different credential. Choose the surface before issuing a credential.
|
|
15
|
+
|
|
16
|
+
The `/v1` API is a beta contract; the `v1` path does not promise an indefinite compatibility window. The TypeScript Managed Agents SDK is an unpublished candidate, so these instructions use standard HTTP and Node's built-in `fetch`. Do not install an unpublished SDK or infer support from an internal architecture proposal.
|
|
17
|
+
|
|
18
|
+
## Find the guide in an installed package
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm root -g
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Open `<the printed directory>/@skrr-ai/cli/docs/v1/README.md`. The example is `docs/v1/examples/api-readonly.mjs` alongside this guide. The same files can be distributed to a docs host without private-source links; a public website deployment is a separate release step.
|
|
25
|
+
|
|
26
|
+
## What has been validated
|
|
27
|
+
|
|
28
|
+
The example's pagination, structured errors and request IDs, bounded retries, cancellation, deadlines, cursor failures, and credential/origin boundaries are checked using a fake HTTP transport. `check-examples.mjs` also checks all relative links in this guide set. These checks do not create accounts, mint keys, spend model credits, or prove an external person's login on a released binary. Human sign-in, public package installation on each supported OS, and docs-host availability remain release acceptance checks.
|
package/docs/v1/api.md
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# Your first Managed Agents API request
|
|
2
|
+
|
|
3
|
+
[Guide index](README.md) · [CLI quickstart](quickstart.md) · [Recovery](operations.md)
|
|
4
|
+
|
|
5
|
+
Use the public `/v1` API for Managed Agents, environments, sessions, events and streaming. Use the workspace CLI for Spaces, Projects and Tasks. The credentials for these surfaces are different.
|
|
6
|
+
|
|
7
|
+
## Create a scoped API key
|
|
8
|
+
|
|
9
|
+
From your own signed-in terminal, choose a Space you administer and grant only the reads needed for this example:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
skrr api-keys create --space <space-id> --name "first-read" --scope agents:read --scope sessions:read --scope events:read
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
The `osk_live_*` or `osk_test_*` secret is shown once. Save it in your secret manager. It is bound to one Space; a key cannot grant broader access than its owner. Current agent listing additionally selects the key creator's published Managed Agents, so an empty list is a successful result even when other workspace agents exist. A `test` prefix is not a promise of free compute or a separate isolated backend.
|
|
16
|
+
|
|
17
|
+
Provide the secret to the example as `SKRR_API_KEY` through your secret manager or a hidden prompt. Do not put it in a command argument, source file or support transcript. The base URL for this deployment is `https://skrr.ai`; self-hosted deployments must use their own issuing origin.
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
node <installed-package>/docs/v1/examples/api-readonly.mjs
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
The example only sends `GET /v1/agents?limit=100`, follows `next_cursor` with `after`, and prints a count. It creates no session or agent run. A zero count is normal on a new account. It reports stable HTTP error codes and the `X-Request-Id` header without logging the key. Set `SKRR_API_BASE_URL` only to the origin that issued your key. Non-loopback HTTP, URL credentials, redirects and cross-origin paths are refused.
|
|
24
|
+
|
|
25
|
+
A read response has this shape:
|
|
26
|
+
|
|
27
|
+
```json
|
|
28
|
+
{"data":[],"has_more":false,"next_cursor":null}
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
List limits are endpoint-specific. Agent/session list requests accept `limit` up to 100, default 20, and an opaque `after` cursor. Do not derive cursors from the public agent ID. Read the returned `next_cursor`; the example refuses missing or repeating cursors instead of looping forever.
|
|
32
|
+
|
|
33
|
+
## Read and reconnect a session
|
|
34
|
+
|
|
35
|
+
For an existing session, events have monotonically increasing sequence numbers. `GET /v1/sessions/<id>/events?from_seq=0&limit=500` is the recovery surface. The response includes `data`, `from_seq`, `max_seq` and `has_more`. Persist the greatest **processed** event `seq`, not `max_seq` (which can describe events beyond this page). The example exports `readEventPage()` and deliberately leaves event processing and durable checkpointing to the application.
|
|
36
|
+
|
|
37
|
+
`GET /v1/sessions/<id>/stream?from_seq=<processed-seq>` opens SSE. Reconnect from your durable cursor and deduplicate by session/sequence. Treat malformed data, an unexpected content type, premature EOF, a deadline or cancellation as explicit outcomes. A stopped HTTP stream alone does not prove the agent finished; inspect the session and durable events. Current replay/stream convergence and fleet-wide connection limits are being hardened; do not assume unlimited history in a single connection or an unconditional delivery guarantee.
|
|
38
|
+
|
|
39
|
+
This guide does not claim a TypeScript SDK candidate has already been published or qualified for your environment. Use your tested SSE parser or a verified SDK release when one is available; do not parse arbitrary transport chunks with `split('\n\n')` alone.
|
|
40
|
+
|
|
41
|
+
## Writes, quotas and billing
|
|
42
|
+
|
|
43
|
+
Create agents and environments, then sessions, only after choosing model, tools, runtime continuity and budget. Grant the corresponding write scopes separately. Event POSTs accept an `Idempotency-Key`; retain the same key and body when retrying the same user turn, and choose a new key for a new turn. A request ID is diagnostic correlation, not an idempotency key.
|
|
44
|
+
|
|
45
|
+
Session creation and run admission can fail for plan, capacity, running-session or session-hours limits before work starts. Authenticated responses can include `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Respect returned limits instead of hardcoding a plan allowance. The example retries only reads and only up to its configured budget. See [quota and cost](operations.md#quota-and-cost).
|
|
46
|
+
|
|
47
|
+
## Rotate and revoke
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
skrr api-keys list --space <space-id> --json
|
|
51
|
+
skrr api-keys rotate <key-id> --space <space-id>
|
|
52
|
+
skrr api-keys revoke <key-id> --space <space-id>
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Rotation immediately invalidates the previous secret; coordinate client configuration changes. Revocation cannot reveal the secret again. Workspace CLI PATs are managed by `skrr tokens`, independent CLI refresh families by `skrr token`; neither is a substitute for `api-keys`.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Developer guide changelog
|
|
2
|
+
|
|
3
|
+
[Guide index](README.md)
|
|
4
|
+
|
|
5
|
+
## 2026-10-10 — first versioned guide edition
|
|
6
|
+
|
|
7
|
+
Source baseline: CLI 0.1.95, daemon 0.8.116. This entry records documentation changes, not an npm or native release.
|
|
8
|
+
|
|
9
|
+
- Added a first-Task CLI journey and a read-only Managed Agents API journey.
|
|
10
|
+
- Distinguished CLI sessions, PATs, daemon bootstrap credentials and Space-scoped API keys.
|
|
11
|
+
- Corrected legacy password-login, `~/.sky` storage and blanket MFA/federation restrictions.
|
|
12
|
+
- Explained the difference between a CLI per-daemon logout sweep and account-wide session/PAT revocation.
|
|
13
|
+
- Added examples for request IDs, pagination, bounded read retries, cancellation, CLI JSONL semantics and stable-key `create-tree --resume` recovery.
|
|
14
|
+
- Preserved historical architecture reviews with dated current-source pointers.
|
|
15
|
+
|
|
16
|
+
Before describing this edition as publicly shipped, verify the npm tarball contains these files, install it without private-source access on each supported platform, exercise a real account's login/logout/recovery, and verify the selected docs host serves the same edition. A local mocked example pass is not those acceptance results.
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/** Offline validation: real example + fake transport, relative-link and documented CLI contracts. */
|
|
3
|
+
import assert from 'node:assert/strict';
|
|
4
|
+
import { readFile, readdir, stat } from 'node:fs/promises';
|
|
5
|
+
import { dirname, resolve } from 'node:path';
|
|
6
|
+
import { fileURLToPath } from 'node:url';
|
|
7
|
+
import { createReadClient, PublicApiError } from './examples/api-readonly.mjs';
|
|
8
|
+
|
|
9
|
+
const root = dirname(fileURLToPath(import.meta.url));
|
|
10
|
+
let checks = 0;
|
|
11
|
+
async function check(name, action) {
|
|
12
|
+
await action();
|
|
13
|
+
console.log(`PASS ${name}`);
|
|
14
|
+
checks++;
|
|
15
|
+
}
|
|
16
|
+
const key = 'osk_test_example_fixture_not_a_secret';
|
|
17
|
+
const json = (body, status = 200, headers = {}) => new Response(JSON.stringify(body), {
|
|
18
|
+
status, headers: { 'content-type': 'application/json', ...headers },
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
await check('read-only first success and opaque cursor pagination', async () => {
|
|
22
|
+
const calls = [];
|
|
23
|
+
const client = createReadClient({ apiKey: key, fetchImpl: async (url, opts) => {
|
|
24
|
+
calls.push({ url: String(url), opts });
|
|
25
|
+
return calls.length === 1
|
|
26
|
+
? json({ data: [{ id: 'ag_one' }], has_more: true, next_cursor: 'opaque-cursor' })
|
|
27
|
+
: json({ data: [{ id: 'ag_two' }], has_more: false, next_cursor: null });
|
|
28
|
+
} });
|
|
29
|
+
assert.deepEqual((await client.listAgents()).map(a => a.id), ['ag_one', 'ag_two']);
|
|
30
|
+
assert.equal(new URL(calls[1].url).searchParams.get('after'), 'opaque-cursor');
|
|
31
|
+
for (const { opts } of calls) {
|
|
32
|
+
assert.equal(opts.method, 'GET');
|
|
33
|
+
assert.equal(opts.redirect, 'error');
|
|
34
|
+
assert.equal(opts.headers.Authorization, `Bearer ${key}`);
|
|
35
|
+
assert(opts.signal instanceof AbortSignal);
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
await check('empty account is a successful response', async () => {
|
|
39
|
+
const client = createReadClient({ apiKey: key, fetchImpl: async () => json({ data: [], has_more: false }) });
|
|
40
|
+
assert.deepEqual(await client.listAgents(), []);
|
|
41
|
+
});
|
|
42
|
+
await check('structured HTTP error and request ID without raw message', async () => {
|
|
43
|
+
const client = createReadClient({ apiKey: key, fetchImpl: async () => json(
|
|
44
|
+
{ error: { code: 'key_expired', message: key } }, 401, { 'x-request-id': 'test-request-1' }),
|
|
45
|
+
});
|
|
46
|
+
await assert.rejects(client.listAgents(), error => error instanceof PublicApiError &&
|
|
47
|
+
error.code === 'key_expired' && error.requestId === 'test-request-1' && !error.message.includes(key));
|
|
48
|
+
});
|
|
49
|
+
await check('server-echoed secrets never become diagnostic codes or request IDs', async () => {
|
|
50
|
+
const client = createReadClient({ apiKey: key, fetchImpl: async () => json(
|
|
51
|
+
{ error: { code: key, message: key } }, 401, { 'x-request-id': key }),
|
|
52
|
+
});
|
|
53
|
+
await assert.rejects(client.listAgents(), error => error instanceof PublicApiError &&
|
|
54
|
+
error.code === 'unexpected_response' && error.requestId === null && !error.message.includes(key));
|
|
55
|
+
});
|
|
56
|
+
await check('429 retry-after honored inside deadline', async () => {
|
|
57
|
+
let calls = 0;
|
|
58
|
+
let clock = 0;
|
|
59
|
+
const waits = [];
|
|
60
|
+
const client = createReadClient({ apiKey: key, now: () => clock, sleep: async ms => { waits.push(ms); clock += ms; },
|
|
61
|
+
fetchImpl: async () => ++calls === 1
|
|
62
|
+
? json({ error: { code: 'rate_limited' } }, 429, { 'retry-after': '1' })
|
|
63
|
+
: json({ data: [], has_more: false }),
|
|
64
|
+
});
|
|
65
|
+
await client.listAgents();
|
|
66
|
+
assert.deepEqual(waits, [1000]);
|
|
67
|
+
assert.equal(calls, 2);
|
|
68
|
+
});
|
|
69
|
+
await check('429 exceeding deadline is surfaced, not retried early', async () => {
|
|
70
|
+
let calls = 0;
|
|
71
|
+
const client = createReadClient({ apiKey: key, timeoutMs: 1000,
|
|
72
|
+
fetchImpl: async () => { calls++; return json({ error: { code: 'rate_limited' } }, 429, { 'retry-after': '60' }); },
|
|
73
|
+
sleep: async () => assert.fail('must not sleep past deadline'),
|
|
74
|
+
});
|
|
75
|
+
await assert.rejects(client.listAgents(), error => error.status === 429);
|
|
76
|
+
assert.equal(calls, 1);
|
|
77
|
+
});
|
|
78
|
+
await check('transient network retry is bounded', async () => {
|
|
79
|
+
let calls = 0;
|
|
80
|
+
const client = createReadClient({ apiKey: key, fetchImpl: async () => { calls++; throw new TypeError('network'); }, sleep: async () => {} });
|
|
81
|
+
await assert.rejects(client.listAgents(), TypeError);
|
|
82
|
+
assert.equal(calls, 3);
|
|
83
|
+
});
|
|
84
|
+
await check('cancellation prevents retry', async () => {
|
|
85
|
+
const controller = new AbortController();
|
|
86
|
+
let calls = 0;
|
|
87
|
+
const client = createReadClient({ apiKey: key, fetchImpl: async () => { calls++; controller.abort(); throw controller.signal.reason; } });
|
|
88
|
+
await assert.rejects(client.listAgents({ signal: controller.signal }), error => error.name === 'AbortError');
|
|
89
|
+
assert.equal(calls, 1);
|
|
90
|
+
});
|
|
91
|
+
await check('per-request deadline aborts the transport', async () => {
|
|
92
|
+
const client = createReadClient({ apiKey: key, timeoutMs: 10, fetchImpl: async (_url, { signal }) =>
|
|
93
|
+
new Promise((_, reject) => {
|
|
94
|
+
const timer = setTimeout(() => reject(new Error('fixture failed to observe deadline')), 200);
|
|
95
|
+
signal.addEventListener('abort', () => { clearTimeout(timer); reject(signal.reason); }, { once: true });
|
|
96
|
+
}),
|
|
97
|
+
});
|
|
98
|
+
await assert.rejects(client.listAgents(), error => error.name === 'TimeoutError');
|
|
99
|
+
});
|
|
100
|
+
await check('repeated/missing cursors and page budget fail explicitly', async () => {
|
|
101
|
+
for (const cursor of [null, 'same']) {
|
|
102
|
+
const client = createReadClient({ apiKey: key, fetchImpl: async () => json({ data: [], has_more: true, next_cursor: cursor }) });
|
|
103
|
+
await assert.rejects(client.listAgents(), /cursor/);
|
|
104
|
+
}
|
|
105
|
+
const client = createReadClient({ apiKey: key, fetchImpl: async () => json({ data: [], has_more: true, next_cursor: 'next' }) });
|
|
106
|
+
await assert.rejects(client.listAgents({ maxPages: 1 }), /budget/);
|
|
107
|
+
});
|
|
108
|
+
await check('incorrect content type fails explicitly', async () => {
|
|
109
|
+
const client = createReadClient({ apiKey: key, fetchImpl: async () => new Response('<html>login</html>', { headers: { 'content-type': 'text/html' } }) });
|
|
110
|
+
await assert.rejects(client.listAgents(), /JSON/);
|
|
111
|
+
});
|
|
112
|
+
await check('credential type, issuing origin and redirects stay separate', async () => {
|
|
113
|
+
assert.throws(() => createReadClient({ apiKey: 'osk_pat_wrong_surface' }), /Space-scoped/);
|
|
114
|
+
assert.throws(() => createReadClient({ apiKey: key, baseURL: 'http://skrr.ai' }), /HTTPS/);
|
|
115
|
+
assert.throws(() => createReadClient({ apiKey: key, baseURL: 'https://user:pass@skrr.ai' }), /origin/);
|
|
116
|
+
assert.throws(() => createReadClient({ apiKey: key, baseURL: 'https://skrr.ai/v1' }), /origin/);
|
|
117
|
+
const client = createReadClient({ apiKey: key, fetchImpl: async () => assert.fail('no HTTP call expected') });
|
|
118
|
+
await assert.rejects(client.get('https://other.invalid/v1/agents'), /same-origin/);
|
|
119
|
+
await assert.rejects(client.get('/v1/../api/user'), /same-origin/);
|
|
120
|
+
});
|
|
121
|
+
await check('event recovery uses processed sequence and encoded session ID', async () => {
|
|
122
|
+
const client = createReadClient({ apiKey: key, fetchImpl: async url => {
|
|
123
|
+
assert.equal(url.pathname, '/v1/sessions/session%2Fid/events');
|
|
124
|
+
assert.equal(url.searchParams.get('from_seq'), '17');
|
|
125
|
+
return json({ data: [{ seq: 18 }], max_seq: 999, has_more: false });
|
|
126
|
+
} });
|
|
127
|
+
const page = await client.readEventPage('session/id', 17);
|
|
128
|
+
assert.equal(page.data[0].seq, 18);
|
|
129
|
+
// The guide leaves max_seq unacknowledged: only processed data may be checkpointed.
|
|
130
|
+
await assert.rejects(client.readEventPage('session', -1), /nonnegative/);
|
|
131
|
+
});
|
|
132
|
+
await check('all packaged guide links resolve', async () => {
|
|
133
|
+
for (const file of (await readdir(root)).filter(f => f.endsWith('.md'))) {
|
|
134
|
+
const content = await readFile(resolve(root, file), 'utf8');
|
|
135
|
+
for (const match of content.matchAll(/\[[^\]]*\]\(([^)]+)\)/g)) {
|
|
136
|
+
const target = match[1];
|
|
137
|
+
if (/^(https?:|mailto:|#)/.test(target)) continue;
|
|
138
|
+
const [filePath, anchor] = target.split('#');
|
|
139
|
+
const resolvedPath = resolve(root, decodeURIComponent(filePath));
|
|
140
|
+
assert((await stat(resolvedPath)).isFile(), `${file}: ${target}`);
|
|
141
|
+
if (anchor) {
|
|
142
|
+
const targetContent = await readFile(resolvedPath, 'utf8');
|
|
143
|
+
const anchors = [...targetContent.matchAll(/^#{1,6}\s+(.+)$/gm)].map(m => m[1].toLowerCase().replace(/[`*]/g, '').replace(/[^a-z0-9 _-]/g, '').replace(/ /g, '-'));
|
|
144
|
+
assert(anchors.includes(anchor), `${file}: missing anchor ${target}`);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
assert(!content.includes('oversky.com'), `${file}: stale production domain`);
|
|
148
|
+
assert(!/skrr login[^\n]*(--password|--email)/.test(content), `${file}: unsupported password login`);
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
await check('documented command names and flags match the installed manifest', async () => {
|
|
152
|
+
const manifest = JSON.parse(await readFile(resolve(root, '../../oclif.manifest.json'), 'utf8'));
|
|
153
|
+
let commands = 0;
|
|
154
|
+
for (const file of (await readdir(root)).filter(f => f.endsWith('.md'))) {
|
|
155
|
+
const content = await readFile(resolve(root, file), 'utf8');
|
|
156
|
+
for (const fence of content.matchAll(/```bash\n([\s\S]*?)```/g)) {
|
|
157
|
+
for (const line of fence[1].split('\n')) {
|
|
158
|
+
if (!line.startsWith('skrr ')) continue;
|
|
159
|
+
const words = line.slice(5).match(/"[^"]*"|'[^']*'|[^\s]+/g) ?? [];
|
|
160
|
+
if (words[0] === '--version') continue;
|
|
161
|
+
let entry;
|
|
162
|
+
for (let count = Math.min(words.length, 5); count > 0; count--) {
|
|
163
|
+
const id = words.slice(0, count).join(':');
|
|
164
|
+
if (manifest.commands[id]) { entry = manifest.commands[id]; break; }
|
|
165
|
+
}
|
|
166
|
+
assert(entry, `${file}: command not in manifest: ${line}`);
|
|
167
|
+
for (const flag of line.matchAll(/(?:^|\s)--([a-z][a-z0-9-]*)/g)) {
|
|
168
|
+
if (flag[1] === 'help') continue;
|
|
169
|
+
assert(Object.hasOwn(entry.flags, flag[1]), `${file}: ${entry.id} has no --${flag[1]}`);
|
|
170
|
+
}
|
|
171
|
+
commands++;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
assert(commands > 0, 'No documented CLI examples were checked.');
|
|
176
|
+
console.log(`Checked ${commands} command examples against manifest ${manifest.version}.`);
|
|
177
|
+
});
|
|
178
|
+
console.log(`Validated ${checks} offline example and guide contracts; no live acceptance claimed.`);
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/** Read-only /v1 example. No dependencies, writes, session creation, or secret logging. */
|
|
3
|
+
import { setTimeout as delay } from 'node:timers/promises';
|
|
4
|
+
import { pathToFileURL } from 'node:url';
|
|
5
|
+
|
|
6
|
+
export class PublicApiError extends Error {
|
|
7
|
+
constructor(status, code, requestId) {
|
|
8
|
+
super(`HTTP ${status}: ${code}`);
|
|
9
|
+
this.name = 'PublicApiError';
|
|
10
|
+
this.status = status;
|
|
11
|
+
this.code = code;
|
|
12
|
+
this.requestId = requestId;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function createReadClient({
|
|
17
|
+
apiKey,
|
|
18
|
+
baseURL = 'https://skrr.ai',
|
|
19
|
+
fetchImpl = fetch,
|
|
20
|
+
timeoutMs = 20_000,
|
|
21
|
+
maxRetries = 2,
|
|
22
|
+
sleep = (ms, signal) => delay(ms, undefined, { signal }),
|
|
23
|
+
now = Date.now,
|
|
24
|
+
} = {}) {
|
|
25
|
+
if (typeof apiKey !== 'string' || !/^osk_(live|test)_[A-Za-z0-9_-]+$/.test(apiKey)) {
|
|
26
|
+
throw new Error('SKRR_API_KEY must be a Space-scoped osk_live_* or osk_test_* key.');
|
|
27
|
+
}
|
|
28
|
+
const origin = new URL(baseURL);
|
|
29
|
+
const loopback = ['localhost', '127.0.0.1', '[::1]'].includes(origin.hostname);
|
|
30
|
+
if (origin.username || origin.password || origin.search || origin.hash || origin.pathname !== '/') {
|
|
31
|
+
throw new Error('Use the issuing origin only, without credentials, a path, query or fragment.');
|
|
32
|
+
}
|
|
33
|
+
if (origin.protocol !== 'https:' && !(origin.protocol === 'http:' && loopback)) {
|
|
34
|
+
throw new Error('Use HTTPS; HTTP is permitted only on loopback for local development.');
|
|
35
|
+
}
|
|
36
|
+
if (!Number.isSafeInteger(timeoutMs) || timeoutMs < 1 || timeoutMs > 120_000 ||
|
|
37
|
+
!Number.isSafeInteger(maxRetries) || maxRetries < 0 || maxRetries > 5) {
|
|
38
|
+
throw new Error('timeoutMs must be 1–120000; maxRetries must be 0–5.');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async function get(path, { signal } = {}) {
|
|
42
|
+
const url = new URL(path, origin);
|
|
43
|
+
if (url.origin !== origin.origin || !url.pathname.startsWith('/v1/')) {
|
|
44
|
+
throw new Error('Only same-origin /v1 read paths are allowed.');
|
|
45
|
+
}
|
|
46
|
+
const deadline = now() + timeoutMs;
|
|
47
|
+
const boundedSignal = signal
|
|
48
|
+
? AbortSignal.any([signal, AbortSignal.timeout(timeoutMs)])
|
|
49
|
+
: AbortSignal.timeout(timeoutMs);
|
|
50
|
+
for (let attempt = 0; ; attempt++) {
|
|
51
|
+
boundedSignal.throwIfAborted();
|
|
52
|
+
if (now() >= deadline) throw new Error('Read deadline exceeded.');
|
|
53
|
+
let response;
|
|
54
|
+
try {
|
|
55
|
+
response = await fetchImpl(url, {
|
|
56
|
+
method: 'GET', redirect: 'error',
|
|
57
|
+
headers: { Authorization: `Bearer ${apiKey}`, Accept: 'application/json' },
|
|
58
|
+
signal: boundedSignal,
|
|
59
|
+
});
|
|
60
|
+
} catch (error) {
|
|
61
|
+
// The example retries only network failures on GET, never writes or canceled reads.
|
|
62
|
+
if (boundedSignal.aborted || !(error instanceof TypeError) || attempt >= maxRetries) throw error;
|
|
63
|
+
const wait = 250 * 2 ** attempt;
|
|
64
|
+
if (now() + wait >= deadline) throw new Error('Read retry would exceed the deadline.');
|
|
65
|
+
await sleep(wait, boundedSignal);
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
const contentType = response.headers.get('content-type') ?? '';
|
|
69
|
+
const body = /\bapplication\/json\b/i.test(contentType) ? await response.json() : null;
|
|
70
|
+
if (response.ok) {
|
|
71
|
+
if (!body || typeof body !== 'object') throw new Error('Expected a JSON object response.');
|
|
72
|
+
return body;
|
|
73
|
+
}
|
|
74
|
+
const rawCode = body?.error?.code;
|
|
75
|
+
const code = typeof rawCode === 'string' && /^[A-Za-z0-9_-]{1,100}$/.test(rawCode) && !rawCode.includes(apiKey)
|
|
76
|
+
? rawCode : 'unexpected_response';
|
|
77
|
+
const rawRequestId = response.headers.get('x-request-id');
|
|
78
|
+
const requestId = rawRequestId && /^[A-Za-z0-9._:-]{1,200}$/.test(rawRequestId) && !rawRequestId.includes(apiKey) ? rawRequestId : null;
|
|
79
|
+
const failure = new PublicApiError(response.status, code, requestId);
|
|
80
|
+
if (![429, 500, 502, 503, 504].includes(response.status) || attempt >= maxRetries) throw failure;
|
|
81
|
+
const retryAfter = response.headers.get('retry-after');
|
|
82
|
+
const seconds = retryAfter && /^\d+(\.\d+)?$/.test(retryAfter) ? Number(retryAfter) : null;
|
|
83
|
+
const date = retryAfter && seconds === null ? Date.parse(retryAfter) : NaN;
|
|
84
|
+
const wait = seconds !== null ? seconds * 1000
|
|
85
|
+
: Number.isFinite(date) ? Math.max(0, date - now()) : 250 * 2 ** attempt;
|
|
86
|
+
if (!Number.isFinite(wait) || now() + wait >= deadline) throw failure;
|
|
87
|
+
await sleep(wait, boundedSignal);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async function listAgents({ signal, maxPages = 100 } = {}) {
|
|
92
|
+
if (!Number.isSafeInteger(maxPages) || maxPages < 1) throw new Error('maxPages must be positive.');
|
|
93
|
+
const agents = [];
|
|
94
|
+
const cursors = new Set();
|
|
95
|
+
let after;
|
|
96
|
+
for (let pageNumber = 0; pageNumber < maxPages; pageNumber++) {
|
|
97
|
+
const query = new URLSearchParams({ limit: '100' });
|
|
98
|
+
if (after) query.set('after', after);
|
|
99
|
+
const page = await get(`/v1/agents?${query}`, { signal });
|
|
100
|
+
if (!Array.isArray(page.data) || typeof page.has_more !== 'boolean') {
|
|
101
|
+
throw new Error('Invalid agent page contract.');
|
|
102
|
+
}
|
|
103
|
+
agents.push(...page.data);
|
|
104
|
+
if (!page.has_more) return agents;
|
|
105
|
+
if (typeof page.next_cursor !== 'string' || !page.next_cursor || cursors.has(page.next_cursor)) {
|
|
106
|
+
throw new Error('Missing or repeated pagination cursor; refusing an infinite loop.');
|
|
107
|
+
}
|
|
108
|
+
cursors.add(page.next_cursor);
|
|
109
|
+
after = page.next_cursor;
|
|
110
|
+
}
|
|
111
|
+
throw new Error('Page budget exhausted; resume with an explicit application checkpoint.');
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
async function readEventPage(sessionId, fromSeq = 0, { signal } = {}) {
|
|
115
|
+
if (typeof sessionId !== 'string' || !sessionId || !Number.isSafeInteger(fromSeq) || fromSeq < 0) {
|
|
116
|
+
throw new Error('A session ID and nonnegative safe-integer fromSeq are required.');
|
|
117
|
+
}
|
|
118
|
+
return get(`/v1/sessions/${encodeURIComponent(sessionId)}/events?from_seq=${fromSeq}&limit=500`, { signal });
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return { get, listAgents, readEventPage };
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
async function main() {
|
|
125
|
+
const controller = new AbortController();
|
|
126
|
+
const cancel = () => controller.abort(new Error('Canceled by the caller.'));
|
|
127
|
+
process.once('SIGINT', cancel);
|
|
128
|
+
try {
|
|
129
|
+
const client = createReadClient({ apiKey: process.env.SKRR_API_KEY, baseURL: process.env.SKRR_API_BASE_URL });
|
|
130
|
+
const agents = await client.listAgents({ signal: controller.signal });
|
|
131
|
+
console.log(JSON.stringify({ ok: true, agentCount: agents.length }));
|
|
132
|
+
} catch (error) {
|
|
133
|
+
// Never log a raw HTTP/network message: it can contain private URLs or payloads.
|
|
134
|
+
console.error(JSON.stringify({ ok: false, error: {
|
|
135
|
+
code: error instanceof PublicApiError ? error.code : controller.signal.aborted ? 'canceled' : 'read_failed',
|
|
136
|
+
...(error instanceof PublicApiError ? { status: error.status, requestId: error.requestId } : {}),
|
|
137
|
+
} }));
|
|
138
|
+
process.exitCode = controller.signal.aborted ? 130 : 1;
|
|
139
|
+
} finally {
|
|
140
|
+
process.removeListener('SIGINT', cancel);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) await main();
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# Operate and recover an integration
|
|
2
|
+
|
|
3
|
+
[Guide index](README.md) · [Quickstart](quickstart.md) · [Public API](api.md)
|
|
4
|
+
|
|
5
|
+
## Authentication and revocation
|
|
6
|
+
|
|
7
|
+
Configuration defaults to `~/.skrr/cli-config.json`; named profiles use `~/.skrr/profiles/<name>/cli-config.json`. `SKRR_CONFIG_DIR` (preferred) and `OVERSKY_CONFIG_DIR` can set another root. macOS stores CLI sessions in the Keychain under `ai.skrr.cli.<profile>` with a config-root suffix where needed. File storage uses `cli-auth.json` (0600) in that profile's directory. The old `~/.sky/config.json` is a migration input, not the current store.
|
|
8
|
+
|
|
9
|
+
The session is bound to its issuing server. Use `skrr whoami` to inspect the account, server, profile and token source. Use named profiles for independent deployments; a failed login to a different deployment restores the old URL rather than reusing its credential on the new origin.
|
|
10
|
+
|
|
11
|
+
| Credential | Purpose | Remove it |
|
|
12
|
+
| --- | --- | --- |
|
|
13
|
+
| CLI refresh family | Human terminal; renews on use | `skrr logout` locally; `skrr token list` / `skrr token revoke <id>` for other CLI families |
|
|
14
|
+
| Daemon credential | Tools on a machine | `skrr revoke-daemon <id>`; revoke associated delegated CLI families too |
|
|
15
|
+
| `osk_pat_*` | CI/automation as the account | `skrr tokens list` / `skrr tokens revoke <id>` |
|
|
16
|
+
| Space `osk_live_*` / `osk_test_*` | Managed Agents `/v1` | `skrr api-keys revoke <id> --space <space-id>` |
|
|
17
|
+
| `osk_ci_*` | Bootstrap a daemon | `skrr revoke-token <id>`; ordinary CLI commands reject it |
|
|
18
|
+
|
|
19
|
+
`skrr logout` clears its stored CLI session and disables automatic durable adoption until the next login. A separately signed-in daemon may still lend the CLI a short-lived token. `skrr logout --all-devices` additionally sweeps registered daemon resources, not independent browser/device-code CLI families or PATs. Inspect partial-revocation warnings. Account Active Sessions **sign out everywhere** uses a broader server revoke-all operation for refresh families and PATs. `skrrd logout --all-devices` attempts that endpoint too, but it requires an authorized human-scope credential; inspect its response and do not mistake local cleanup after an authorization/network failure for completed remote revocation. Space API keys still have their own management surface.
|
|
20
|
+
|
|
21
|
+
For CI, create an expiring PAT from a human sign-in and inject it through `SKRR_TOKEN`. Read scope is the default; write scope permits account-authorized workspace changes but not sensitive human-only routes such as creating credentials, payments or account recovery. Accounts with two-factor authentication need fresh verification when creating a PAT. `OVERSKY_TOKEN` wins over `SKRR_TOKEN` if both are set; injected credentials never refresh. Do not share a rotating refresh bundle across jobs.
|
|
22
|
+
|
|
23
|
+
## Classify the error
|
|
24
|
+
|
|
25
|
+
Workspace CLI JSON failures expose `ok: false` and `error` with a stable code, message and available details/retryability. Public `/v1` failures expose `{ "error": { "code": "...", "message": "..." } }`. They are different envelopes. Always record the process exit status or HTTP status as well.
|
|
26
|
+
|
|
27
|
+
| Outcome | Action |
|
|
28
|
+
| --- | --- |
|
|
29
|
+
| `NOT_SIGNED_IN`, `SESSION_REVOKED`, `REFRESH_REUSED` | Verify profile/server, then explicitly sign in; never silently replace a deliberately revoked family |
|
|
30
|
+
| `/v1` `missing_auth`, `invalid_key`, `key_revoked`, `key_expired` | Check the issuing origin and correct credential type; rotate/revoke through the owner account |
|
|
31
|
+
| `insufficient_scope`, `PAT_SCOPE_INSUFFICIENT`, `PAT_HUMAN_ONLY_ROUTE` | Choose the intended surface and authority; do not automatically widen scopes |
|
|
32
|
+
| `STEP_UP_REQUIRED` | Use `skrr step-up` from a human session; a PAT cannot pass a human step-up |
|
|
33
|
+
| HTTP 429 | Respect `Retry-After`, cap retries and inspect quota type |
|
|
34
|
+
| HTTP 503 `not_configured` | Deployment operator action; repeated credential creation cannot configure the service |
|
|
35
|
+
| Network error / 504 on a write | Outcome unknown until readback; reconcile before retrying |
|
|
36
|
+
|
|
37
|
+
Capture `X-Request-Id` from response headers. Include timestamp with timezone, origin, CLI version, command/endpoint, HTTP/exit status and stable code when reporting an issue. Exclude Authorization, tokens, recovery codes, private prompts and full sensitive payloads. A single timeout does not demonstrate that every service is unavailable.
|
|
38
|
+
|
|
39
|
+
## Recover an uncertain write
|
|
40
|
+
|
|
41
|
+
For Tasks, use stable `external-key` values and read the target Space after a failure. Match intended key, scope and payload before considering an item created. Bulk operations can commit some items before a lost response; retain the full input file and reconcile created/existing/failed/unknown per item. Retry only missing items with their original keys. The current candidate CLI adds `--resume` for this recovery:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
skrr tasks create-tree --from-json tree.json --space <space-id> --external-key-prefix integration/initial-import --resume --json
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Use the **unchanged original input**, the same Space/workspace and the same external-key prefix. Do not use `--fresh`, change temp IDs, or alter content-derived keys after an uncertain result. Resume confirms the IDs of existing nodes without patching them and creates only missing nodes; intentionally editing existing Tasks is a separate operation. This flag must exist in your installed artifact (`skrr commands --schema "tasks create-tree"`), not merely in source. If a server supplies a durable operation receipt, retain and query that ID; client reconciliation is not a substitute for a durable server receipt.
|
|
48
|
+
|
|
49
|
+
For public event writes, preserve `Idempotency-Key` and the same body for the same turn. Do not automatically retry arbitrary POSTs merely because they failed with 5xx. For reads, bounded retry with cancellation and a request deadline is appropriate; the bundled example demonstrates it.
|
|
50
|
+
|
|
51
|
+
For stream recovery, checkpoint only processed event sequences, reopen from that cursor, deduplicate, and inspect durable state on EOF. A connected socket is not a completion receipt. Persist application state before acknowledging work to your user.
|
|
52
|
+
|
|
53
|
+
The workspace CLI also has a separate agent-turn stream (this starts work and can consume quota):
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
skrr agents chat <agent-id> --prompt "Summarize the chosen repository" --jsonl
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Each stdout line is one JSON event. `text.delta.text` is the cumulative answer despite the event name: take the latest value rather than appending it repeatedly. A `run.result` is a terminal result; `stream.gap` describes missing envelopes, and a lost view is not proof the run failed. Preserve the process exit status: 0 for completed, 1 for a failed terminal result, 75 for lost streaming access, and 130 for actual Ctrl-C detach. Cancellation is requested but must be verified against durable work state. CLI JSONL is a different transport contract from public SSE; this guide's offline checks validate the command/flags, while real reconnect/lifetime qualification belongs to the installed CLI's release checks.
|
|
60
|
+
|
|
61
|
+
## Quota and cost
|
|
62
|
+
|
|
63
|
+
Authentication, account authorization, API RPM, model quota, balance, hosted capacity and session-hours admission are different limits. A live/test API-key prefix does not make execution free. Starting an agent, invoking inference or allocating hosted compute can consume quota or credits. Use the account's Balance/usage surface and applicable plan controls before running work; the CLI command named `usage` is command-discovery guidance, not a billing statement. Prices and entitlements are deployment-specific and can change; this guide does not freeze a price list.
|
|
64
|
+
|
|
65
|
+
Canceling your HTTP request stops the caller waiting; it does not necessarily cancel a durable remote Run. Use the relevant session/task lifecycle command to cancel the work, then verify its terminal state and any runtime lease. Runtime continuity distinguishes one-shot, host-owned, true-resume and restart-with-disk products; a preserved disk alone does not prove a running process survived.
|
|
66
|
+
|
|
67
|
+
## Permissions and local machine access
|
|
68
|
+
|
|
69
|
+
Review machine consent, repository scope, harness mode and connector permissions separately. A workspace role does not replace local machine consent. Plan/read-only modes must not mutate; broader modes allow tools to edit or run commands within their effective sandbox. On a personal machine, same-user processes are not separate OS security identities. Do not interpret a policy label or Git worktree as a credential boundary.
|
|
70
|
+
|
|
71
|
+
Inspect `skrr code --help` and your machine/agent configuration before starting work. An unattended permission request may be denied instead of accepted. A network-enabled sandbox and a write boundary are separate properties. Keep credentials out of agent child processes; delegated agent sessions use session credentials and cannot run human login/logout.
|
|
72
|
+
|
|
73
|
+
## Upgrade and support
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
skrr --version
|
|
77
|
+
skrr doctor --json
|
|
78
|
+
npm install -g @skrr-ai/cli@latest
|
|
79
|
+
skrr --version
|
|
80
|
+
skrr daemon status
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
The npm CLI and signed native daemon are separately promoted artifacts. Updating the CLI does not prove a daemon or hosted guest image updated. Let the managed daemon installer/updater verify the pinned signed feed; do not work around verification failures by downloading unsigned binaries. Record the before/after versions and retry a read-only command after upgrading.
|
|
84
|
+
|
|
85
|
+
For a failed CLI install, inspect Node version, npm prefix/PATH and package/plugin completeness before changing credentials. A missing `dist` in a source checkout requires a build; an architecture-specific dependency copied from another OS is not proof that the public tarball is broken. Keep the failing output and environment so support can distinguish those cases.
|
|
86
|
+
|
|
87
|
+
Use the product's available support entry point or your deployment administrator and include the redacted evidence above. Do not assume a private GitHub issue link is accessible to an external user. See the [changelog](changelog.md) for this guide's scope and release gates.
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# First success with skrr
|
|
2
|
+
|
|
3
|
+
[Guide index](README.md) · [Public API](api.md) · [Recovery](operations.md)
|
|
4
|
+
|
|
5
|
+
You need Node.js 20+ and an account on [skrr](https://skrr.ai) or your own deployment. The CLI can manage workspaces without installing a local daemon. A daemon is needed when agents execute tools on your own machine.
|
|
6
|
+
|
|
7
|
+
## Install and choose your account
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install -g @skrr-ai/cli
|
|
11
|
+
skrr --version
|
|
12
|
+
skrr login --base-url https://skrr.ai
|
|
13
|
+
skrr whoami
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Sign-in first tries a local daemon already signed in to the same deployment, then browser PKCE, then device code when the callback cannot run. Your web account handles password, MFA, passkey and configured federation. For SSH or a machine without a browser:
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
skrr login --device-code
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Approve the displayed URL and code from another device. Ctrl-C cancels. You do not need password flags, and the CLI does not receive your password. If you need an independent browser/device-code family instead of a daemon-brokered one, this flag skips the broker too.
|
|
23
|
+
|
|
24
|
+
CLI login can offer to install and sign in the local background service. Declining that offer still leaves the CLI signed in. `SKRR_SKIP_DAEMON_HANDOFF=1` skips the optional setup. [Credential storage and revocation](operations.md#authentication-and-revocation) explains what each sign-out actually removes.
|
|
25
|
+
|
|
26
|
+
Before changing anything, confirm the account, profile, deployment and workspace:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
skrr context --json
|
|
30
|
+
skrr spaces list --json
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Choose an existing Space where you have edit access. For a team workspace, select it explicitly with `--workspace <workspace-id>` on the commands below. Do not rely on an old terminal's default scope.
|
|
34
|
+
|
|
35
|
+
## Create one Task
|
|
36
|
+
|
|
37
|
+
Run a dry run first; substitute the chosen Space ID:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
skrr tasks create --space <space-id> --title "My first CLI task" --execution-mode human --external-key onboarding/first-task --dry-run --json
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Then create the Task:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
skrr tasks create --space <space-id> --title "My first CLI task" --execution-mode human --external-key onboarding/first-task --json
|
|
47
|
+
skrr tasks show <task-id-from-create> --json
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
`human` avoids assigning a default execution agent. Creation itself does not start an agent Run; a Space's configured automation can react to new tasks, so choose a Space with automation disabled when learning. Reusing a stable `external-key` lets you find the same task after an uncertain response. Use a different key when you intend to create a different Task.
|
|
51
|
+
|
|
52
|
+
If a write times out, first list/read by that key and inspect the existing Task. A timeout means the response is unknown; it does not prove the write failed. Never retry a large create-tree by assuming that none of its items committed. See [write recovery](operations.md#recover-an-uncertain-write).
|
|
53
|
+
|
|
54
|
+
## Decide where an agent should run
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
skrr code --help
|
|
58
|
+
skrr daemon status
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
`skrr code` in a repository starts interactive coding work and can edit files and consume model/compute quota. Choose the agent, machine, repository and permissions before starting it. A personal machine must remain available for its tools; cloud execution requires the appropriate hosted product and plan. A Task, a CLI session, and a machine's daemon session have separate lifecycles.
|
|
62
|
+
|
|
63
|
+
## Use command discovery
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
skrr commands --json --brief
|
|
67
|
+
skrr commands --schema "tasks create"
|
|
68
|
+
skrr doctor --json
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Prefer a schema for your installed version over copied flags from an old review. Machine callers consume JSON stdout and the process exit status; guidance and warnings may still be on stderr. [Operations](operations.md) covers script failures and support evidence.
|