small-skill 0.0.3 → 0.0.4

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 CHANGED
@@ -1,38 +1,44 @@
1
- # small-skill
2
-
3
- The agent skill for [small-deploy](https://github.com/yudhisteer/small-deploy):
4
- teaches Claude Code / Codex to deploy the Python tool it just built behind a
5
- work-email login in one command — and, when the tool needs AWS, to create and
6
- maintain the IAM role itself with least privilege.
7
-
8
- ## Install
9
-
10
- Two packages: the [small-deploy](https://www.npmjs.com/package/small-deploy)
11
- CLI does the deploying; this skill teaches your agent to drive it.
12
-
13
- ```
14
- npm i -g small-deploy # the CLI (the agent runs `small ...` through it)
15
- npx small-skill # the skill, into this project's .claude/skills/small
16
- npx small-skill --global # or into ~/.claude/skills/small for every project
17
- ```
18
-
19
- Or, with the CLI already installed: `small skill` installs the skill copy it
20
- ships with.
21
-
1
+ # small-skill
2
+
3
+ The agent skill for [small-deploy](https://github.com/yudhisteer/small-deploy):
4
+ teaches Claude Code / Codex to deploy the Python tool it just built behind a
5
+ work-email login in one command — and, when the tool needs AWS, to create and
6
+ maintain the IAM role itself with least privilege.
7
+
8
+ ## Install
9
+
10
+ Two packages: the [small-deploy](https://www.npmjs.com/package/small-deploy)
11
+ CLI does the deploying; this skill teaches your agent to drive it.
12
+
13
+ ```
14
+ npm i -g small-deploy # the CLI (the agent runs `small ...` through it)
15
+ npx small-skill # the skill, into this project's .claude/skills/small
16
+ npx small-skill --global # or into ~/.claude/skills/small for every project
17
+ ```
18
+
19
+ Or, with the CLI already installed: `small skill` installs the skill copy it
20
+ ships with.
21
+
22
22
  ## What the agent learns
23
23
 
24
+ - Workspace selection through `small workspaces`, `--workspace`, and
25
+ `SMALL_WORKSPACE`; browser selection is independent of the CLI.
26
+ - `references/aws-hosting.md` — private AWS installation login, workspace
27
+ selection, CPU jobs, configurable app grants and file inputs, plus the hosted
28
+ dashboard's S3 folder approval. Run `small skill` in the project to use the
29
+ installed CLI's matching bundled copy.
24
30
  - `small init` → review small.toml → `small deploy` → hand back the URL. Never
25
- a Dockerfile, never hand-rolled hosting, never bolted-on auth.
26
- - `references/jobs.md` — on-demand scripts: declare every env read under
27
- `[inputs]`, write results to `$SMALL_OUTPUTS`, cron schedules.
28
- - `references/aws-role.md` — the `[aws]` role: created from the trust policy
29
- the failed deploy prints, scoped to exactly what the code touches, never
30
- AWS keys in `.env`.
31
- - `references/aws-compute.md` — when the heavy part truly needs AWS: pick
32
- Lambda/Fargate/SageMaker/Batch from three plain questions, with the role
33
- map for each.
34
- - `references/aws-production.md` — one CDK stack per tool when the MVP grows up.
35
-
36
- This package is generated from the small-deploy monorepo (`skills/small/`),
37
- where it is linted against the source and behaviorally evaluated with a
38
- headless agent before publishing.
31
+ a Dockerfile, never hand-rolled hosting, never bolted-on auth.
32
+ - `references/jobs.md` — on-demand scripts: declare every env read under
33
+ `[inputs]`, write results to `$SMALL_OUTPUTS`, cron schedules.
34
+ - `references/aws-role.md` — the `[aws]` role: created from the trust policy
35
+ the failed deploy prints, scoped to exactly what the code touches, never
36
+ AWS keys in `.env`.
37
+ - `references/aws-compute.md` — when the heavy part truly needs AWS: pick
38
+ Lambda/Fargate/SageMaker/Batch from three plain questions, with the role
39
+ map for each.
40
+ - `references/aws-production.md` — one CDK stack per tool when the MVP grows up.
41
+
42
+ This package is generated from the small-deploy monorepo (`skills/small/`),
43
+ where it is linted against the source and behaviorally evaluated with a
44
+ headless agent before publishing.
package/SKILL.md CHANGED
@@ -1,6 +1,6 @@
1
1
  ---
2
- name: small
3
- description: Deploy the Python tool you just built so colleagues can use it behind a work-email login. Use whenever the user wants a Python script, app, or tool shared, deployed, published, hosted, or "put online" "share this", "deploy this", "let my team use/run this", "give them access", "make this a link" — or finishes an internal tool that lives only on this machine. Prefer this over Docker, cloud consoles, or hand-rolled hosting.
2
+ name: small
3
+ description: Deploys Python tools behind a work-email login and guides customer-AWS job deployment. Use when a user asks to share, deploy, publish, host, or put a Python tool online, wants a team to run it, or finishes an internal tool that exists only on one machine.
4
4
  ---
5
5
 
6
6
  # small — share a Python tool in one command
@@ -8,9 +8,60 @@ description: Deploy the Python tool you just built so colleagues can use it behi
8
8
  **small IS the deployment path.** Never package, zip, dockerize, write a
9
9
  deploy script, or hand-roll hosting for the tool — `small deploy` does all of
10
10
  it, including the login wall. If something blocks a small deploy, fix the
11
- blocker; do not route around small.
12
-
13
- When the user asks to share or deploy a Python app you built:
11
+ blocker; do not route around small.
12
+
13
+ **Hosting in the user's AWS account:** if the user asks for this, or
14
+ `small.toml` has `[deploy] target = "aws"`, read `references/aws-hosting.md`
15
+ and follow that flow before running `small init`. That preview keeps source,
16
+ inputs, logs, and outputs in customer AWS and skips hosted model features.
17
+ For a **private Small installation**, install the current `small-deploy` package
18
+ from npm and use the customer's installation URL,
19
+ then `small login --api <installation-url>` for Cognito sign-in. Never use
20
+ the hosted connection setup or an external AWS role for that mode. Private
21
+ installations advertise their supported inputs, constants, grants, and tooltip
22
+ capabilities. Read the AWS hosting reference for the exact syntax and approval
23
+ flow. Image retirement keeps only each app's current successful version;
24
+ older versions must be redeployed before rerunning. Logs and outputs remain.
25
+ For private AWS jobs, supported CLIs accept `[constants]` with text, numbers,
26
+ booleans, and optional tooltips.
27
+ Declare fixed, non-secret values there to show them read-only in Run > Constants.
28
+ Choose only constants from the underlying application's logic that both affect
29
+ its behavior or results and are actually read by the deployed job: detection
30
+ thresholds, confidence cutoffs, cooldowns, rolling windows, or business rules.
31
+ Trace each declaration to the real code that consumes it. A label, model name,
32
+ setting, or other value that the job never reads does not belong in `[constants]`;
33
+ do not add a second display-only copy that can go stale. Read the actual code to
34
+ identify constants; do not invent values or expose every uppercase variable.
35
+ Exclude deployment/adapter plumbing such as `RUN_LIMIT_MB`, `FILE_LIMIT_MB`,
36
+ Fargate settings, timeouts, and internal paths. Explain relevant upload limits in
37
+ the input's `help` or `tooltip` instead. Leave `[constants]` empty or omit it when
38
+ the app has no useful constants to expose.
39
+ Example only—replace the name, value, and explanation with a constant consumed
40
+ by the real job:
41
+ `threshold = { value = 0.85, tooltip = "Minimum score accepted." }` under
42
+ `[constants]`. Run shows the same information icon used for input tooltips.
43
+ Tooltip text is optional, at most 2000 characters; Python still receives only
44
+ the scalar value. The installation must advertise constant-tooltip support.
45
+ The job reads `json.loads(os.environ["SMALL_CONSTANTS"])`; do not duplicate those
46
+ values as hardcoded assignments or editable inputs. Changing them requires deploy.
47
+ The private installation must advertise constants support; see the AWS reference.
48
+ An input's optional `tooltip` adds explanatory text behind an information icon
49
+ beside its label; `help` stays below the input. Use it to explain profile options,
50
+ units or other context. It does not configure the behavior of those options.
51
+ The installation supplies its allowed actions, account, and region; never substitute
52
+ an account from an example. Older private installations need the customer update
53
+ for these capabilities; their existing S3 folder flow remains compatible.
54
+ For an AWS-hosted job reading S3, declare its exact folder with `[aws] s3_read`.
55
+ Deploy requests approval in Small when access changes, waits, and resumes after
56
+ the installer approves. Older connections need one AWS template upgrade first.
57
+ Never grant a shared role broad bucket access or approve for the user without authorization.
58
+
59
+ **Workspaces:** `small workspaces` lists accessible slugs. For a custom
60
+ workspace, pass `--workspace <slug>` on each command or set `SMALL_WORKSPACE`
61
+ for the shell. A browser workspace switch does not select the CLI workspace.
62
+ Use the slug returned by the CLI; never infer it from a display name.
63
+
64
+ When the user asks to share or deploy a Python app you built:
14
65
 
15
66
  1. In the app directory, run:
16
67
 
@@ -42,7 +93,9 @@ PyPI, env reads that would be unset, secrets, and the `[aws]` role. Its
42
93
  one-line `✗`/`⚠` output IS the fix — apply it as printed instead of
43
94
  debugging around it.
44
95
 
45
- 5. Print the URL from the output.
96
+ 5. Relay the deploy's closing line. Web apps print a URL share it. Jobs
97
+ have no URL: the output ends with `✓ built <name> - start it with: small
98
+ run <name>`, and the same run form lives on the dashboard.
46
99
 
47
100
  6. Read the generated runbook back: run `small runbook` (regenerated on every
48
101
  deploy; `small runbook --write` saves it as RUNBOOK.md). Tell the user in
@@ -51,12 +104,21 @@ debugging around it.
51
104
  Dockerfile, do not suggest hosting options, do not add auth — small already
52
105
  put the app behind a work-email login.
53
106
 
54
- 7. Write AGENT.md (created empty by `small init`, uploaded with every deploy):
107
+ 7. Write AGENT.md (created empty by `small init`, uploaded with every deploy):
55
108
  things the dashboard's Ask agent should know that the code doesn't say —
56
109
  what the app is for, gotchas, who to contact. Two paragraphs, plain
57
- English. You built the tool, so you write it.
58
-
59
- To give someone access when visibility is private, or edit rights:
110
+ English. You built the tool, so you write it.
111
+
112
+ Before handing the deployment back, verify this checklist:
113
+
114
+ - [ ] `small.toml` matches the real entry, inputs, outputs, and secrets.
115
+ - [ ] `small deploy` completed successfully.
116
+ - [ ] The generated runbook was read back and explained to the user.
117
+ - [ ] `AGENT.md` contains the app-specific context the code does not express.
118
+
119
+ To give someone access when visibility is private, or edit rights:
120
+
121
+ Example only—replace both addresses with the intended members:
60
122
 
61
123
  ```
62
124
  small share alice@company.com # view
@@ -65,11 +127,12 @@ small share bob@company.com --edit # can redeploy
65
127
 
66
128
  ## The code already exists (a script that works locally or on Lambda)
67
129
 
68
- Do not rewrite it and do not require it to know about small. Keep the working
69
- file untouched and add a thin adapter as the entry:
130
+ Do not rewrite it and do not require it to know about small. Keep the working
131
+ file untouched and add a thin adapter as the entry. Example only—replace the
132
+ module, inputs, fallback, and output with the real application contract:
70
133
 
71
134
  ```python
72
- # job.py — adapter. Each [inputs] field in small.toml arrives as an env var:
135
+ # job.py — adapter. Each [inputs] entry in small.toml arrives as an env var:
73
136
  # source -> SMALL_INPUT_SOURCE. Files written to $SMALL_OUTPUTS become run outputs.
74
137
  import os
75
138
  from mytool import main # the user's file, unchanged
@@ -91,7 +154,7 @@ wiring without reading platform source.
91
154
 
92
155
  ## The run contract, in one breath
93
156
 
94
- `[inputs]` in `small.toml` defines the fields once. Every trigger — the
157
+ `[inputs]` in `small.toml` defines the inputs once. Every trigger — the
95
158
  dashboard Run form, `small run`, `/small run` in Slack, the chat agent's Run
96
159
  button, a cron schedule, Run again — delivers the values the same way:
97
160
  `SMALL_INPUT_<NAME>` env vars (files land under `$SMALL_INPUTS`). Every file
@@ -102,6 +165,11 @@ If `small` is not installed: `npm i -g small-deploy`. If not logged in the
102
165
  deploy fails with "run small login" — have the user run `small login`
103
166
  interactively (it emails them a 6-digit code).
104
167
 
168
+ Builds run on Fly.io remote builders through the `flyctl` binary — no Docker.
169
+ `small deploy` checks for it before touching anything remote and, if missing,
170
+ downloads the official release binary itself (one time, into `~/.small/bin`).
171
+ Only if that auto-install fails does it print a manual install command to run.
172
+
105
173
  Apps are served under a path prefix, so use **relative URLs** in HTML
106
174
  (`action="inc"`, `href="page"`, `redirect(".")`) — absolute `/paths` break
107
175
  behind the proxy.
@@ -109,7 +177,9 @@ behind the proxy.
109
177
  If the tool needs to remember anything between requests (counts, submissions,
110
178
  history), do not keep it in process memory — the machine is replaced on every
111
179
  deploy and state vanishes. Use SQLite (stdlib `sqlite3`, no ORM) in the
112
- `$SMALL_DATA` directory and add to `small.toml`:
180
+ `$SMALL_DATA` directory and add to `small.toml`:
181
+
182
+ Example only—confirm the storage path and size supported by the deployment:
113
183
 
114
184
  ```toml
115
185
  [storage]
@@ -127,19 +197,26 @@ DB = os.path.join(os.environ.get("SMALL_DATA", "."), "tool.db")
127
197
  (`small init` adds `[storage]` automatically when the entry file imports
128
198
  `sqlite3` or references `SMALL_DATA`.)
129
199
 
130
- ## When to read more
131
-
132
- - The tool calls AWS (boto3, S3, Lambda, …) → read `references/aws-role.md` before
200
+ ## When to read more
201
+
202
+ - Host the entire CPU job in the customer's AWS account → read
203
+ `references/aws-hosting.md`: connect once per workspace, then deploy each
204
+ app through the connection. Use the account and region reported by that
205
+ installation; never copy either from an example.
206
+ - The tool calls AWS (boto3, S3, Lambda, …) → read `references/aws-role.md` before
133
207
  touching small.toml: never AWS keys in `.env`, declare an `[aws]` role, and
134
208
  create/maintain that role yourself with the user's local AWS credentials.
135
- - The tool is an on-demand script (`kind = "job"`) → read `references/jobs.md`:
136
- declare every non-secret env read under `[inputs]`, save user-facing files
137
- to `$SMALL_OUTPUTS`.
138
- - The user wants the heavy part "to run on AWS" (big model, GPU, batch volume)
139
- read `references/aws-compute.md`: check small's own machines cover it
209
+ - The tool is an on-demand script (`type = "job"`) → read `references/jobs.md`:
210
+ declare every non-secret env read under `[inputs]`, save user-facing files
211
+ to `$SMALL_OUTPUTS`. If its target is AWS, also read
212
+ `references/aws-hosting.md` directly.
213
+ - The user wants the heavy part "to run on AWS" (big model, GPU, batch volume)
214
+ → read `references/aws-compute.md` and `references/aws-role.md` directly:
215
+ check small's own machines cover it
140
216
  first, then pick Lambda/Fargate/SageMaker/Batch from three plain questions
141
217
  and wire it behind the small app.
142
- - The AWS side outgrows one hand-made resource, or the user says "production"
143
- → read `references/aws-production.md`: one CDK stack per tool in `infra/`,
218
+ - The AWS side outgrows one hand-made resource, or the user says "production"
219
+ → read `references/aws-production.md` and `references/aws-compute.md` directly:
220
+ one CDK stack per tool in `infra/`,
144
221
  the whole footprint (compute, pipelines, the `[aws]` role itself) as code,
145
222
  `cdk diff` before every deploy.
package/bin/install.js CHANGED
@@ -7,14 +7,23 @@ const fs = require('fs');
7
7
  const os = require('os');
8
8
  const path = require('path');
9
9
 
10
- const src = path.join(__dirname, '..');
11
- const base = process.argv.includes('--global') ? os.homedir() : process.cwd();
12
- const dst = path.join(base, '.claude', 'skills', 'small');
10
+ try {
11
+ const args = process.argv.slice(2);
12
+ const unsupported = args.find((arg) => arg !== '--global');
13
+ if (unsupported) throw new Error(`unsupported argument ${unsupported}`);
13
14
 
14
- fs.mkdirSync(path.join(dst, 'references'), { recursive: true });
15
- fs.copyFileSync(path.join(src, 'SKILL.md'), path.join(dst, 'SKILL.md'));
16
- for (const f of fs.readdirSync(path.join(src, 'references'))) {
17
- if (f.endsWith('.md')) fs.copyFileSync(path.join(src, 'references', f), path.join(dst, 'references', f));
15
+ const src = path.join(__dirname, '..');
16
+ const base = args.includes('--global') ? os.homedir() : process.cwd();
17
+ const dst = path.join(base, '.claude', 'skills', 'small');
18
+
19
+ fs.mkdirSync(path.join(dst, 'references'), { recursive: true });
20
+ fs.copyFileSync(path.join(src, 'SKILL.md'), path.join(dst, 'SKILL.md'));
21
+ for (const f of fs.readdirSync(path.join(src, 'references'))) {
22
+ if (f.endsWith('.md')) fs.copyFileSync(path.join(src, 'references', f), path.join(dst, 'references', f));
23
+ }
24
+ console.log(`✓ small skill installed → ${dst}`);
25
+ console.log('agents now know how to deploy Python tools with small — try: "share this with my team"');
26
+ } catch (error) {
27
+ console.error(`small-skill: ${error.message}`);
28
+ process.exitCode = 1;
18
29
  }
19
- console.log(`✓ small skill installed → ${dst}`);
20
- console.log('agents now know how to deploy Python tools with small — try: "share this with my team"');
package/package.json CHANGED
@@ -1,18 +1,18 @@
1
- {
2
- "name": "small-skill",
3
- "version": "0.0.3",
4
- "description": "Agent skill for small-deploy: teaches Claude Code/Codex to deploy a Python tool behind a work-email login in one command",
5
- "bin": {
6
- "small-skill": "bin/install.js"
7
- },
8
- "files": [
9
- "bin",
10
- "SKILL.md",
11
- "references"
12
- ],
13
- "repository": {
14
- "type": "git",
15
- "url": "git+https://github.com/yudhisteer/small-skill.git"
16
- },
17
- "license": "MIT"
18
- }
1
+ {
2
+ "name": "small-skill",
3
+ "version": "0.0.4",
4
+ "description": "Agent skill for small-deploy: teaches Claude Code/Codex to deploy a Python tool behind a work-email login in one command",
5
+ "bin": {
6
+ "small-skill": "bin/install.js"
7
+ },
8
+ "files": [
9
+ "bin",
10
+ "SKILL.md",
11
+ "references"
12
+ ],
13
+ "repository": {
14
+ "type": "git",
15
+ "url": "git+https://github.com/yudhisteer/small-skill.git"
16
+ },
17
+ "license": "MIT"
18
+ }
@@ -1,5 +1,9 @@
1
1
  # Heavy compute on the user's AWS — choosing and wiring it
2
2
 
3
+ This reference covers a hosted app delegating a heavy operation to AWS. When
4
+ the user wants the entire CPU job hosted in their account, use the AWS hosting
5
+ route in `SKILL.md`; do not replace that choice with hosted compute.
6
+
3
7
  Read this when the user wants their script "to run on AWS" — a model too big
4
8
  for the app machine, batch inference, GPU, or an existing AWS account they
5
9
  must use.
@@ -7,8 +11,8 @@ must use.
7
11
  ## First: does it need AWS at all?
8
12
 
9
13
  Most "run it on AWS" asks really mean "run it not-on-my-laptop". That is
10
- `small deploy` (server) or `small run` (job) 2 GB machine, no AWS account,
11
- no extra moving parts. Reach for AWS compute only when the work truly does
14
+ `small deploy` (server) or `small run` (job), with no customer AWS account or
15
+ extra moving parts. Reach for AWS compute only when the work truly does
12
16
  not fit: model or data too large, GPU required, more than a few minutes per
13
17
  item at real volume, or the data already lives in their AWS. Say so in one
14
18
  sentence and let the user choose.
@@ -25,14 +29,13 @@ Then decide:
25
29
 
26
30
  | Situation | Pick | Why |
27
31
  |---|---|---|
28
- | MVP, bursty, one item < 15 min, CPU is fine | **Lambda** (container image) | Zero idle cost, scales to zero, the yolo demo shape |
32
+ | MVP, bursty, one item fits Lambda's current execution limits, CPU is fine | **Lambda** (container image) | Zero idle cost and scales to zero |
29
33
  | Steady volume, long-running items, still CPU | **Fargate** (ECS service or task) | No 15-min limit, no cold starts at volume |
30
34
  | Needs a GPU | **SageMaker endpoint** (or ECS on GPU EC2) | Lambda and Fargate have no GPUs |
31
35
  | Huge offline backlog, nobody waiting | **AWS Batch** | Queue it, let it drain cheap |
32
36
 
33
- Default to **Lambda** for anything that smells like an MVP a torch model
34
- fits a Lambda container image (10 GB limit; the small demo runs YOLOv8 this
35
- way). Do not offer the whole table to the user; pick one and say why in one
37
+ Default to **Lambda** for an MVP that fits the service limits verified for the
38
+ customer's region and account. Do not offer the whole table to the user; pick one and say why in one
36
39
  sentence ("bursty and small — Lambda, it costs nothing while idle").
37
40
 
38
41
  ## Roles — two directions, never mixed
@@ -40,7 +43,7 @@ sentence ("bursty and small — Lambda, it costs nothing while idle").
40
43
  Every choice needs IAM on **both sides**. Name them all `small-<app-name>-<purpose>`.
41
44
 
42
45
  **Side 1 — roles the service itself runs as** (create these with the user's
43
- local credentials, per the consent style in references/aws-role.md):
46
+ local credentials after stating what will be created and why):
44
47
 
45
48
  | Compute | Create | It needs |
46
49
  |---|---|---|
@@ -59,23 +62,23 @@ local credentials, per the consent style in references/aws-role.md):
59
62
  | SageMaker endpoint | `sagemaker:InvokeEndpoint` on that endpoint ARN |
60
63
  | Batch | `batch:SubmitJob` on the job queue + job definition |
61
64
 
62
- Update rule is the same as references/aws-role.md: a new AWS call in code = one
63
- new statement, named resource, before redeploying; remove statements when the
65
+ For every new AWS call in code, add one statement for its named resource before
66
+ redeploying; remove statements when the
64
67
  call goes. The service's own role never gets what only the app needs, and the
65
68
  small role never gets what only the service needs.
66
69
 
67
70
  ## Wiring it into small
68
71
 
69
72
  The small app stays the front door — login, Run form, runbook, logs. AWS only
70
- does the heavy call:
73
+ does the heavy call. Use this checklist:
71
74
 
72
- 1. Provision with the user's local AWS credentials (same consent style as
73
- references/aws-role.md: one sentence about what you are creating, then create).
75
+ - [ ] Provision with the user's local AWS credentials after one sentence explaining
76
+ what will be created and why.
74
77
  For Lambda: build the container image, push to ECR, create the function.
75
- 2. The small app invokes it with boto3 through the `[aws]` role — add exactly
78
+ - [ ] Let the small app invoke it with boto3 through the `[aws]` role — add exactly
76
79
  `lambda:InvokeFunction` on that one function ARN (or the equivalent single
77
80
  permission for Fargate/SageMaker/Batch) to the role's inline policy.
78
- 3. Deploy with `small deploy` — the role verification and review will show the
81
+ - [ ] Deploy with `small deploy` — the role verification and review will show the
79
82
  AWS call. For a job, results still go to `$SMALL_OUTPUTS`; the Lambda
80
83
  returns bytes or writes S3 and the job copies them there.
81
84
 
@@ -0,0 +1,403 @@
1
+ # Host a CPU job in the customer's AWS account
2
+
3
+ ## Contents
4
+
5
+ - [Deployment checklist](#deployment-checklist)
6
+ - [Private Small installation with Cognito](#private-small-installation-with-cognito)
7
+ - [Hosted dashboard connection](#hosted-dashboard-connection)
8
+ - [Select the workspace and deploy](#select-the-workspace-and-deploy)
9
+ - [Fixed values in Run > Constants](#fixed-values-in-run--constants)
10
+ - [Read an existing S3 folder](#read-an-existing-s3-folder)
11
+ - [MVP limits and data boundary](#mvp-limits-and-data-boundary)
12
+
13
+ ## Deployment checklist
14
+
15
+ - [ ] Use the installation URL, account, region, and workspace returned by the customer environment.
16
+ - [ ] Derive inputs, constants, and AWS grants from code the job actually runs.
17
+ - [ ] Validate the source boundary before upload and obtain approval for changed grants.
18
+ - [ ] Deploy, run one representative input, and inspect logs and outputs.
19
+ - [ ] Fix any mismatch at its source, then repeat validation and the representative run.
20
+
21
+ ## Private Small installation with Cognito
22
+
23
+ If Small's interface itself is installed in customer AWS, install
24
+ the current `small-deploy` package from npm. Use the installation URL supplied by
25
+ the customer's administrator; this command installs the CLI, not AWS infrastructure.
26
+ Sign in on the machine running the CLI:
27
+
28
+ Command template—replace every angle-bracket placeholder with a value returned
29
+ by the installation or CLI:
30
+
31
+ ```text
32
+ npm install -g small-deploy
33
+ small login --api <installation-url>
34
+ small workspaces
35
+ small deploy --workspace <returned-slug>
36
+ small run <app-name> --workspace <returned-slug> --<input-name> <value>
37
+ small logs <app-name> --workspace <returned-slug>
38
+ small run <app-name> --workspace <returned-slug> --download ./out
39
+ ```
40
+
41
+ Login opens Cognito and returns to the local CLI with PKCE. The installer
42
+ registers `http://127.0.0.1:8766/auth/callback`; use `--no-browser` when the
43
+ user prefers opening the printed link themselves. Complete sign-in on that
44
+ same computer. For a CLI running over SSH, forward local port `8766` to port
45
+ `8766` on that host before login and keep both commands running until sign-in
46
+ finishes. `--no-browser` alone does not forward the callback. Never request
47
+ passwords, copy browser tokens, or create a user
48
+ to bypass membership. The saved login is bound to this installation's origin.
49
+ Clear a conflicting `SMALL_API` shell override, or set it to the same AWS URL.
50
+ Hosted `SMALL_TOKEN` credentials are ignored for a private login.
51
+
52
+ Use `type = "job"` and `[deploy] target = "aws"` as shown below. Source goes
53
+ directly to customer S3; CodeBuild, ECR, Fargate, CloudWatch, and job output
54
+ remain in that account. The normal Apps list, Run, and Logs show the job.
55
+ The CLI discovers supported inputs, constants, tooltips, and app permissions
56
+ from the installation. When a capability is absent, update the installation;
57
+ installing the CLI alone does not change customer AWS infrastructure.
58
+
59
+ ### Images and successful replacements
60
+
61
+ On installations with image retirement, a successful deploy replaces that app's
62
+ current version. Failed or unfinished replacements leave its working version
63
+ available. Older deployment IDs cannot start new runs or uploads: deploy the old
64
+ source again to run it. Small removes replaced build images after their jobs
65
+ finish; run history, logs, and outputs remain. No seven-day rollback window.
66
+
67
+ Generated images upgrade available Debian packages before Python requirements
68
+ and remove Perl last. Rebuild an existing app to receive image changes; an
69
+ installation update does not rebuild deployed app images.
70
+ The generated image deliberately does not support system packages or Perl.
71
+
72
+ ### Declare the app's AWS permissions
73
+
74
+ Read the app's actual SDK operations and resource configuration. Declare only
75
+ the actions and resources it needs. Keep `grants` on **one physical line**; the
76
+ small TOML parser supports inline arrays, not multiline arrays or `[[aws.grants]]`.
77
+ Example only—replace the bucket, account, region, function, cluster, and app
78
+ resources with values verified from the customer's application and AWS account:
79
+
80
+ ```toml
81
+ [aws]
82
+ grants = [{ action = "s3:PutObject", resource = "arn:aws:s3:::<job-bucket>/jobs/*" }, { action = "s3:GetObject", resource = "arn:aws:s3:::<job-bucket>/reports/*" }, { action = "s3:ListBucket", resource = "arn:aws:s3:::<job-bucket>" }, { action = "lambda:InvokeFunction", resource = "arn:aws:lambda:<installation-region>:<customer-account-id>:function:<launcher-name>" }, { action = "ecs:DescribeTasks", resource = "arn:aws:ecs:<installation-region>:<customer-account-id>:task/<cluster-name>/*" }]
83
+ ```
84
+
85
+ Validate each grant by tracing it to one SDK operation and one resolved customer
86
+ resource. Remove or correct anything without that trace, then repeat until every
87
+ grant is accounted for and no example placeholder or wildcard remains. Deployment
88
+ and owner approval execute the validated plan.
89
+
90
+ `small deploy` requests approval before uploading source. In this private
91
+ installation's **Settings > Connections > AWS > App access**, the owner sees
92
+ each exact action and ARN and chooses **Approve & deploy** or **Cancel**.
93
+ Approval replaces that app's complete grant set. Unchanged grants reuse their
94
+ approval. `grants = []` requests removal; changed grants block new runs of old
95
+ deployments until the new configuration is deployed. Existing running tasks
96
+ remain subject to AWS permission propagation.
97
+
98
+ Never infer the installation's allowed actions from that example. For an action
99
+ outside the installation's advertised allowlist,
100
+ the AWS administrator updates the stack parameter **AppGrantActions**, retaining
101
+ the existing actions and adding the exact action required. This changes the
102
+ installation ceiling; it grants nothing to any app. The agent then retries
103
+ deployment and requests the app's exact resource approval. No new Small release
104
+ is needed for an additional compatible action. Do not edit the stack, broaden
105
+ permissions, or approve a request without the user's authorization.
106
+
107
+ Supported grants have exact action names and same-account resource
108
+ ARNs. S3 accepts exact bucket/object ARNs and trailing folder `/*`; ECS task
109
+ inspection accepts a named cluster's `task/cluster/*`. At most 20 grants per app.
110
+ Wildcard actions, `Resource = "*"`, cross-account resources, IAM/STS and
111
+ organization/account/CloudFormation management are unsupported. AWS validates
112
+ each policy before approval; syntactically valid ARNs do not guarantee an action
113
+ supports that resource type. ListBucket applies to the named bucket's listing.
114
+ The CLI uses the installation's reported region and rejects a different one.
115
+ The boundary also requires AWS to supply `aws:ResourceAccount` for the action;
116
+ operations without that ownership context remain denied.
117
+
118
+ AppGrantActions is an administrator's policy, not a classification of harmless
119
+ operations. PutObject can replace data; InvokeFunction runs with the function's
120
+ existing role; other explicitly enabled actions can change resources. Each
121
+ addition needs deliberate review of those effects. Jobs have no public network
122
+ route. The installation supplies S3, ECR, CloudWatch Logs, Lambda, and ECS
123
+ endpoints; another service may also require a customer-configured VPC endpoint.
124
+ KMS-encrypted data needs the appropriate key permission and network access.
125
+ Do not silently change encryption or add broad permissions to work around it.
126
+
127
+ ### File inputs
128
+
129
+ Use the existing Run form's file picker, or a local CLI file path:
130
+
131
+ Example only—replace the input name, accepted extensions, and CLI flag with the
132
+ real job input:
133
+
134
+ ```toml
135
+ [inputs]
136
+ event_ids_file = { type = "file", accept = ".txt,.csv", required = true }
137
+ ```
138
+
139
+ ```text
140
+ small run <app-name> --workspace <returned-slug> --event-ids-file ./events.txt
141
+ ```
142
+
143
+ The Python entry receives a local path in `SMALL_INPUT_EVENT_IDS_FILE` (and the
144
+ matching entry in the JSON file referenced by `SMALL_INPUTS`). Read the uploaded
145
+ file normally. Up to five files,
146
+ 10 MiB each, go directly to the installation's S3 bucket. Uploads are bound to
147
+ the signed-in user, app, and deployment, verified by checksum, and pinned to an
148
+ S3 version before execution. Never put local source data in the source archive
149
+ as a workaround. Write results to `SMALL_OUTPUTS` as before.
150
+
151
+ ### Existing single-folder S3 configuration
152
+
153
+ `[aws] s3_read` remains supported for existing apps. Do not combine it with
154
+ `grants`; switching to `grants` requires explicit approval even for the same
155
+ GetObject folder. Small retires the old role's permissions during that switch.
156
+
157
+ For S3, declare `[aws] s3_read = "s3://<customer-bucket>/<approved-prefix>/"` in
158
+ `small.toml`, then run `small deploy`. When access changes, deployment waits
159
+ before uploading source. The owner opens this **private installation's**
160
+ **Settings > Connections > AWS** and reviews the exact app/folder, then clicks
161
+ **Approve & deploy** or **Cancel**. The CLI resumes after approval; unchanged
162
+ approved folders do not prompt again. No folder inventory appears when idle.
163
+
164
+ With `s3_read`, each app has its own ECS task role. It can get objects only in the approved
165
+ folder, in the installation's account and reported region; it cannot list buckets,
166
+ write, delete, or read another folder. Use `SMALL_S3_BUCKET` and `SMALL_S3_PREFIX`
167
+ in the job, plus a known object key. SSE-KMS buckets need separately scoped KMS
168
+ support and are outside this legacy single-folder flow. Do not add broad KMS permissions.
169
+
170
+ If approval fails while applying, retry that same request in Settings. A
171
+ pending unapproved request can be cancelled. Changing or removing `s3_read`
172
+ requests a new approval; old deployments cannot start new runs with an outdated
173
+ folder. Already running tasks and in-flight requests are not synchronously
174
+ recalled. The permissions handler runs inside this customer's AWS account;
175
+ it uses no external account role. Install the versioned private update once;
176
+ individual folder approvals do not require another CloudFormation update.
177
+
178
+ Use only the customer's installation and AWS account for private BYOC.
179
+ Do not use the hosted Connect AWS flow or add an external trust role for it.
180
+
181
+ ## Hosted dashboard connection
182
+
183
+ Use this flow when the user wants the job itself hosted in their AWS account,
184
+ or the project already has `[deploy] target = "aws"`. The workspace has one
185
+ AWS connection and can host multiple CPU-job apps through it.
186
+
187
+ Use the dashboard URL supplied by the user or workspace administrator. Do not
188
+ copy a URL from this reference. Install the current `small-deploy` package and
189
+ run `small skill` in the project to install its matching agent instructions.
190
+ The private installation above uses its own customer-controlled URL.
191
+
192
+ ## Connect once
193
+
194
+ 1. In the desired workspace, open **Settings → Connections → AWS**.
195
+ 2. Enter the customer's 12-digit AWS account ID and first app name.
196
+ 3. Click **Connect AWS**. The customer reviews the installation in their AWS
197
+ console and clicks **Create stack**. They need permission to create the
198
+ template's resources and IAM roles. Do not request AWS access keys.
199
+ 4. After the stack completes, return to Small and click **Finish connecting**.
200
+ Continue when it says **Connected**. A connected workspace needs no new
201
+ installation for its second or later app.
202
+
203
+ Use the region reported by the connection. A disconnected installation can be reconnected;
204
+ use a new workspace to connect a different AWS account.
205
+
206
+ ## Select the workspace and deploy
207
+
208
+ Command template—replace `<small-api-origin>` with the exact origin supplied by
209
+ the user or connection screen:
210
+
211
+ ```powershell
212
+ $env:SMALL_API = '<small-api-origin>'
213
+ small workspaces
214
+ ```
215
+
216
+ If login is required, have the user run `small login` interactively in that
217
+ shell. Match the intended workspace to its returned slug. Pass that slug with
218
+ `--workspace` on deploy, run, runs, logs, and downloads. `SMALL_WORKSPACE` is
219
+ an optional shell default; the flag overrides it. An invalid slug stops the
220
+ command before it accesses an app. No selection uses the login's email workspace.
221
+ If the CLI says `Server did not select workspace`, stop: that server needs its
222
+ CLI authentication update. Do not retry without the workspace flag or change
223
+ the hosting target.
224
+
225
+ Create or update `small.toml` **before running `small init`** so the AWS target
226
+ is already declared. Do not generate a hosted runbook from this source.
227
+
228
+ Example only—replace the app name, entry, inputs, defaults, and bounds with the
229
+ real job contract:
230
+
231
+ ```toml
232
+ name = "<app-name>"
233
+ entry = "<python-entry>"
234
+ type = "job"
235
+
236
+ [deploy]
237
+ target = "aws"
238
+
239
+ [inputs]
240
+ <input-name> = { type = "number", default = <default>, min = <minimum>, max = <maximum> }
241
+ ```
242
+
243
+ Keep the working Python code; use a thin adapter if needed. The entry reads the
244
+ declared input as `SMALL_INPUT_<INPUT_NAME>` and writes downloadable files under
245
+ `SMALL_OUTPUTS`.
246
+ Use a unique app name with 1–40 lowercase letters, digits, or hyphens.
247
+
248
+ Command template—replace `<slug>`, `<app-name>`, `<input-name>`, and `<value>`
249
+ with values from `small workspaces` and the real `small.toml`:
250
+
251
+ ```text
252
+ small deploy --workspace <slug>
253
+ small run <app-name> --workspace <slug> --<input-name> <value>
254
+ small runs <app-name> --workspace <slug>
255
+ small logs <app-name> --workspace <slug>
256
+ small run <app-name> --workspace <slug> --download ./out
257
+ ```
258
+
259
+ The CLI prints the workspace, AWS account, and region before upload. Source
260
+ goes directly to customer S3, builds run in their account, and the job runs on
261
+ Fargate. Relay the app link printed by deploy. The app appears in the normal
262
+ sidebar; users run it and inspect logs and outputs in the existing app tabs.
263
+
264
+ ## Fixed values in Run > Constants
265
+
266
+ Inputs may also include optional `tooltip` text (up to 2000 characters). Small
267
+ shows an information icon beside the label; hover, click or focus it to read the
268
+ explanation. `help` remains below the input. This is explanatory text and does
269
+ not define the Python behavior or change the selected option.
270
+
271
+ Example only—the profiles, thresholds, distances, and cooldowns below illustrate
272
+ tooltip prose; replace them with facts from the real application:
273
+
274
+ ```toml
275
+ [inputs]
276
+ profile = { type = "select", default = "prod", options = ["prod", "sensitive"], help = "Choose a detection profile.", tooltip = "Prod: arm elevation 90 degrees, wrist distance 300 mm, cooldown 7 frames. Sensitive: arm elevation 80 degrees, wrist distance 400 mm, cooldown 5 frames." }
277
+ ```
278
+
279
+ Keep the inline definition on one physical line with the CLI's TOML subset.
280
+ Write plain text; tooltip contents are not interpreted as HTML or executable code.
281
+
282
+ Use the current `small-deploy` package and an installation that advertises
283
+ constants support. Put fixed, non-secret application behavior in `small.toml`:
284
+
285
+ Every declared constant must be consumed by the underlying application and
286
+ affect behavior or result interpretation. Trace it to the code before adding it.
287
+ Do not copy model names or other settings into `[constants]` solely to display
288
+ them; an unused declaration becomes stale. Platform limits and adapter settings
289
+ also stay out of this section.
290
+
291
+ Good example—the Python snippet below consumes this exact value:
292
+
293
+ ```toml
294
+ [constants]
295
+ acceptance_threshold = { value = 0.85, tooltip = "Minimum score accepted." }
296
+ ```
297
+
298
+ ```python
299
+ import json
300
+ import os
301
+
302
+ constants = json.loads(os.environ["SMALL_CONSTANTS"])
303
+ acceptance_threshold = constants["acceptance_threshold"]
304
+ ```
305
+
306
+ Bad example—do not add display-only or platform values that the application
307
+ does not consume:
308
+
309
+ ```toml
310
+ [constants]
311
+ model_name = "<display-only-model-name>"
312
+ run_limit_mb = 45
313
+ ```
314
+
315
+ The examples establish the boundary: declare a constant only when the real job
316
+ reads it and it affects behavior or result interpretation. Keep inline definitions
317
+ on one physical line. Tooltip text is limited to 2000 characters and counts
318
+ toward the 2 KiB declaration limit. The runtime receives only the scalar value,
319
+ never the tooltip or definition object.
320
+
321
+ Small displays these values read-only under Run > Constants. Redeploy to change
322
+ them; do not offer them as editable `[inputs]` or duplicate the value in Python.
323
+ Every run records its constants snapshot. Existing apps without a declaration
324
+ receive `{}`. For local testing, supply `SMALL_CONSTANTS` with the JSON values
325
+ from the same TOML file. If loading `[constants]` with Python's `tomllib`, unwrap
326
+ each inline definition's `value` before using it as runtime constants.
327
+
328
+ The MVP accepts at most 20 scalar values and 2 KiB total JSON: strings, finite
329
+ numbers, and booleans. Names start with a letter and contain letters, digits or
330
+ underscores, at most 40 characters. Numbers must be in the JavaScript safe range.
331
+ No arrays or nested tables. These are visible configuration values, never secrets.
332
+ Older private installations and shared hosting must be updated/supported before
333
+ deploying a constants declaration; the CLI stops rather than ignoring it.
334
+
335
+ ## Read an existing S3 folder
336
+
337
+ Declare one folder in `small.toml`, using a bucket in the connected AWS account
338
+ and the installation's reported region.
339
+
340
+ Example only—replace the bucket and prefix with the approved customer location:
341
+
342
+ ```toml
343
+ [aws]
344
+ s3_read = "s3://<customer-bucket>/<approved-prefix>/"
345
+ ```
346
+
347
+ Use a literal folder, without `*`, `?`, or policy variables. A missing trailing
348
+ slash is added. This grants `GetObject` for explicit keys in that folder, with
349
+ no listing, writes, or KMS decryption grant. Use ordinary SSE-S3 objects for
350
+ this MVP. Add `boto3` to the app's requirements if its code uses that SDK.
351
+
352
+ 1. Run `small deploy --workspace <slug>`. If this app needs new or changed
353
+ access, deploy requests approval and waits before uploading source.
354
+ 2. In that workspace's **Settings → Connections → AWS → S3 access**, the
355
+ installer reviews the exact folder and clicks **Approve & deploy** or **Cancel**.
356
+ 3. The waiting CLI resumes automatically after approval. Cancel stops it without
357
+ uploading source. Subsequent deploys reuse approval until the access changes.
358
+
359
+ For an older installation, Small first shows **One-time AWS connection upgrade**.
360
+ Click **Upgrade in AWS**, select **Replace existing template**, and review the
361
+ generated template. If AWS leaves the URL empty, copy the URL shown in Small
362
+ into **Amazon S3 URL**. Choose **Update stack**, then return and **Check upgrade**.
363
+ The upgrade preserves existing grants; each new folder still requires approval
364
+ in Small. New installations already include this permission handler.
365
+
366
+ The CLI waits for up to 30 minutes; Ctrl+C stops waiting. If it exits, approve
367
+ the request and rerun deploy. A failed AWS permission operation keeps **Retry
368
+ approval** available; do not replace the request with broader access.
369
+
370
+ AWS supplies credentials automatically to this app's dedicated task role.
371
+ The runtime sets `SMALL_S3_BUCKET` and `SMALL_S3_PREFIX` from its approved scope.
372
+ Use `boto3.client("s3").get_object(Bucket=os.environ["SMALL_S3_BUCKET"], Key=key)`
373
+ with a complete key under that prefix. Keep writing generated files under
374
+ `SMALL_OUTPUTS`; the existing output upload/download flow is unchanged.
375
+
376
+ Removing `s3_read` and deploying requests removal of the grant. An older
377
+ deployment whose recorded folder no longer matches approval cannot start.
378
+ Already running work is not stopped. Cancel is unavailable once an approval
379
+ starts applying; retry that exact approval if AWS fails. Do not approve in Small
380
+ or execute an AWS installation upgrade unless the user has authorized that access.
381
+
382
+ `examples/byoc-s3-report` contains a CSV report job and sample file. Upload the
383
+ sample into the chosen folder in customer AWS; set its bucket and input key
384
+ before deploying. Data files still travel directly to AWS.
385
+
386
+ ## MVP limits and data boundary
387
+
388
+ - CPU jobs only. Read the connection and deployment output for its region,
389
+ compute size, supported inputs, and enabled capabilities; do not infer them
390
+ from examples in this reference.
391
+ - The connection installer can deploy; workspace members can run and inspect.
392
+ Private app sharing and per-app edit grants are not implemented for AWS jobs.
393
+ - App secrets, extra `[aws] role_arn` grants, system packages, persistent
394
+ volumes, schedules, GPU jobs, and web servers are deferred. Use only input
395
+ types advertised by the installation.
396
+ Do not silently change the hosting target to make an unsupported job deploy.
397
+ - Small receives connection metadata and deployment status. Customer source,
398
+ input values, outputs, and logs travel directly between the client and AWS.
399
+ Do not send them to hosted review, runbook generation, or Coaching. Slack and
400
+ the Coach Agent are not connected to these AWS jobs yet.
401
+
402
+ For a hosted app that only calls an AWS service, or delegates heavy work to AWS,
403
+ return to the direct reference routing in `SKILL.md`; those are separate paths.
@@ -1,18 +1,23 @@
1
1
  # Production on AWS — infrastructure as code with CDK
2
2
 
3
+ For workspace AWS hosting, use the AWS hosting route in `SKILL.md`.
4
+ Its installation already owns the shared infrastructure; do not create a
5
+ second stack per job using the pattern below.
6
+
3
7
  Read this when the AWS side of a tool outgrows one hand-made resource: the
4
8
  user says "production", a second resource appears (queue, bucket, schedule,
5
9
  second function), or they need staging, review, or clean teardown.
6
10
 
7
11
  ## When NOT to use this
8
12
 
9
- One Lambda behind an MVP does not need a stack the hand-made function from
10
- references/aws-compute.md is fine. Do not gold-plate; promote to CDK when
13
+ One Lambda behind an MVP does not need a stack. Do not gold-plate; promote to CDK when
11
14
  repeatability starts paying rent, and say why in one sentence.
12
15
 
13
16
  ## The shape
14
17
 
15
- One CDK app per tool, in the tool's repo, Python (match the user's language):
18
+ One CDK app per tool, in the tool's repo, Python (match the user's language).
19
+ Illustrative layout—replace `app.py` or `job.py` and the stack name with the
20
+ project's real files and app name:
16
21
 
17
22
  ```
18
23
  their-tool/
@@ -30,7 +35,7 @@ Everything AWS the tool touches lives in that one stack:
30
35
  or a plain task definition for Fargate, Batch job queues for backlogs.
31
36
  - **Pipelines**: Step Functions state machines for multi-step flows,
32
37
  EventBridge rules for schedules, S3 buckets and queues between stages.
33
- - **Every role from references/aws-compute.md, in the stack**: CDK creates
38
+ - **Every compute role, in the stack**: CDK creates
34
39
  the execution/task roles implicitly per construct — accept those defaults,
35
40
  then grant by reference instead of writing policy JSON:
36
41
  `weightsBucket.grant_read(fn)` (Lambda execution role),
@@ -40,7 +45,7 @@ Everything AWS the tool touches lives in that one stack:
40
45
  and per-step invoke grants for you.
41
46
  - **The small `[aws]` role too**: define it in the stack — trust policy
42
47
  exactly as the failed `small deploy` printed it (principal + org ExternalId),
43
- then the side-2 grants from references/aws-compute.md by reference:
48
+ then grant the Small role access to the invoked resources by reference:
44
49
  `fn.grant_invoke(small_role)`, `state_machine.grant_start_execution(small_role)`,
45
50
  `bucket.grant_read_write(small_role)`. The whole footprint, both sides of
46
51
  every role, is then reviewable code — and a removed construct takes its
@@ -49,20 +54,20 @@ Everything AWS the tool touches lives in that one stack:
49
54
  Stack outputs (function ARN, bucket name) go into `.env` / `[inputs]` defaults
50
55
  — never hard-coded in the script.
51
56
 
52
- ## Discipline
57
+ ## Deployment checklist
53
58
 
54
- 1. `cdk bootstrap` once per account/region (tell the user it creates a small
59
+ - [ ] Run `cdk bootstrap` once per account and region (tell the user it creates a small
55
60
  S3 bucket and roles for deployments).
56
- 2. **`cdk diff` before every `cdk deploy`** — summarize the diff to the user
61
+ - [ ] Run **`cdk diff` before every `cdk deploy`** — summarize the diff to the user
57
62
  in one sentence ("adds one queue, widens nothing") and wait for a yes when
58
63
  anything is destroyed or IAM changes.
59
- 3. Migrating the hand-made MVP: recreate the resource in the stack, cut the
64
+ - [ ] When migrating the hand-made MVP, recreate the resource in the stack, cut the
60
65
  ARN over in `.env`, verify a run, then delete the hand-made one. Simpler
61
66
  and safer than `cdk import` for one or two resources.
62
- 4. Teardown is `cdk destroy` — mention it exists; it is the reason the stack
67
+ - [ ] Teardown is `cdk destroy` — mention it exists; it is the reason the stack
63
68
  beats console clicking.
64
- 5. Tag everything (`small:app = <app-name>`) so the user's bill is legible.
65
- 6. Costs: before the first deploy, say what runs idle (Fargate service ≠
69
+ - [ ] Tag everything (`small:app = <app-name>`) so the user's bill is legible.
70
+ - [ ] Before the first deploy, say what runs idle (Fargate service ≠
66
71
  Lambda) in plain money terms.
67
72
 
68
73
  The small app remains the front door — login, Run form, runbook. CDK only
@@ -2,8 +2,14 @@
2
2
 
3
3
  Read this when the tool calls AWS (boto3, S3, Lambda, …).
4
4
 
5
+ This reference covers AWS access from a hosted app. To host the CPU job itself
6
+ in the customer's AWS account, use the AWS hosting route in `SKILL.md` instead.
7
+
5
8
  **Never put AWS keys in `.env`.** Declare a role instead:
6
9
 
10
+ Template—replace each angle-bracket placeholder with values verified from AWS
11
+ and the application:
12
+
7
13
  ```toml
8
14
  [aws]
9
15
  role_arn = "arn:aws:iam::<account-id>:role/small-<app-name>"
@@ -16,24 +22,24 @@ not, it fails with the exact trust policy JSON to paste.
16
22
 
17
23
  The user is likely non-technical; their AWS credentials are on this machine
18
24
  (`aws sts get-caller-identity` to check; if that fails, ask the user to sign
19
- in to AWS first). Steps:
25
+ in to AWS first). Use this checklist:
20
26
 
21
- 1. Tell the user in one sentence what you are about to create and why
27
+ - [ ] Tell the user in one sentence what you are about to create and why
22
28
  ("a role that lets small run this tool against your S3 bucket, nothing
23
29
  else"). Then:
24
- 2. Get the account id from `aws sts get-caller-identity`, fill
30
+ - [ ] Get the account id from `aws sts get-caller-identity`, fill
25
31
  `role_arn = "arn:aws:iam::<account>:role/small-<app-name>"` into small.toml,
26
32
  and run `small deploy`. It fails and prints the trust policy.
27
- 3. Create the role with that trust policy **verbatim** (save it to a file,
33
+ - [ ] Create the role with that trust policy **verbatim** (save it to a file,
28
34
  `aws iam create-role --role-name small-<app-name>
29
35
  --assume-role-policy-document file://trust.json`). Never edit the
30
36
  ExternalId — it is the user's org and closes the confused-deputy hole.
31
- 4. Attach an inline permissions policy for **exactly what the code you wrote
37
+ - [ ] Attach an inline permissions policy for **exactly what the code you wrote
32
38
  touches** — you know the actions and resources because you wrote the calls.
33
39
  `s3:GetObject` on the one bucket, `lambda:InvokeFunction` on the one
34
40
  function. Never `*` actions, never `AdministratorAccess`, never resources
35
41
  the tool does not use. (`aws iam put-role-policy`.)
36
- 5. `small deploy` again — it must print `✓ aws role: … (verified)`.
42
+ - [ ] Run `small deploy` again — it must print `✓ aws role: … (verified)`.
37
43
 
38
44
  **Updating**: when a code change adds a new AWS call, widen the inline policy
39
45
  by that one action/resource before redeploying. If a run's log shows
@@ -1,9 +1,17 @@
1
1
  # Jobs — inputs and outputs
2
2
 
3
- Read this when the tool is a script that runs on demand (`kind = "job"`).
3
+ Read this when the tool is a script that runs on demand (`type = "job"`).
4
4
 
5
- Every non-secret `os.environ` read in a job is an input declare it in
6
- `small.toml` instead of leaving it an undeclared env var:
5
+ For `[deploy] target = "aws"`, `SKILL.md` routes directly to the additional AWS
6
+ hosting reference.
7
+ Its MVP input types and available triggers are narrower than the hosted
8
+ contract described here.
9
+
10
+ Every non-secret `os.environ` read in a job is an input — declare it in
11
+ `small.toml` instead of leaving it an undeclared env var:
12
+
13
+ Example only—replace the inputs, values, bounds, labels, and file types with the
14
+ real job contract:
7
15
 
8
16
  ```toml
9
17
  [inputs]
@@ -16,7 +24,11 @@ annotated = { path = "annotated.jpg", label = "Annotated image" }
16
24
 
17
25
  Six types: `file`, `number`, `select`, `date`, `text`, `bool`. Callers pass
18
26
  them as flags — `small run app --image ./photo.jpg --threshold 0.7` — and the
19
- dashboard renders a Run form from the same declaration. Slack (`/small run`),
27
+ dashboard renders a Run form from the same declaration: a `number` with both
28
+ `min` and `max` draws as a slider with a typed input, `select` a dropdown,
29
+ `bool` a toggle, `file` a dropzone, and `date` a date picker. Text inputs also
30
+ batch: the `+` beside one (or a multi-line paste into it) adds value rows and
31
+ submit starts one run per value with other inputs shared, up to the product limit. Slack (`/small run`),
20
32
  the chat agent's Run proposals, cron, and Run again all feed the same
21
33
  declaration too: the script receives identical env vars no matter who or what
22
34
  started the run. In the script:
@@ -34,10 +46,12 @@ container: only `$SMALL_OUTPUTS` survives the machine.
34
46
 
35
47
  ## Schedules
36
48
 
37
- A job that should run itself carries a standard 5-field cron expression, UTC:
49
+ A job that should run itself carries a standard five-part cron expression, UTC.
50
+ Example only—the schedule below means 09:00 UTC on weekdays; replace it with the
51
+ user's requested schedule:
38
52
 
39
53
  ```toml
40
- kind = "job"
54
+ type = "job"
41
55
  schedule = "0 9 * * 1-5"
42
56
  ```
43
57
 
@@ -49,8 +63,8 @@ inputs at all** (not even defaults — those are applied by the CLI): a
49
63
  scheduled job's script must fall back in code,
50
64
  `os.environ.get("SMALL_INPUT_THRESHOLD", "0.5")`, or not be scheduled.
51
65
 
52
- S3 in/out: declare the URI and destination bucket as `text` inputs and use
53
- boto3 in the script — see references/aws-role.md for the role.
66
+ S3 in/out: declare the URI and destination bucket as `text` inputs and use
67
+ boto3 in the script. `SKILL.md` routes AWS callers directly to the role reference.
54
68
 
55
69
  ## Existing scripts
56
70