Skip to content

Commit 3ceb99b

Browse files
committed
feat: added openclaw logo
1 parent 821c1dd commit 3ceb99b

35 files changed

Lines changed: 2629 additions & 45 deletions

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,6 @@ yarn-error.log*
4040
# typescript
4141
*.tsbuildinfo
4242
next-env.d.ts
43+
44+
# claws-supply cli local state
45+
.claws-supply/

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ Bun + Turborepo monorepo.
55
## Workspace Layout
66

77
- `apps/web`: Next.js App Router application (`@claws-supply/web`)
8-
- `packages`: reserved for future packages (CLI will live here later)
8+
- `packages/cli`: claws.supply creator CLI (`@claws-supply/cli`)
99

1010
## Requirements
1111

bun.lock

Lines changed: 307 additions & 44 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/cli/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
dist

packages/cli/README.md

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
# claws-supply CLI (`packages/cli`)
2+
3+
MVP creator CLI for claws.supply with four commands:
4+
5+
- `claws-supply auth`
6+
- `claws-supply logout`
7+
- `claws-supply build`
8+
- `claws-supply publish`
9+
10+
No consume/install flow is included in this phase.
11+
12+
## Commands
13+
14+
### `auth`
15+
16+
```bash
17+
claws-supply auth [--client-id <id>] [-D] [--no-open] [--json]
18+
```
19+
20+
What it does:
21+
22+
- Starts Better Auth device authorization via `/api/cli/v1/auth/device/code`
23+
- Opens `verification_uri_complete` (unless `--no-open`)
24+
- Polls `/api/cli/v1/auth/device/token` until approved
25+
- Stores bearer auth state locally
26+
27+
Auth state path:
28+
29+
- `~/.config/claws-supply/auth.json` (or `$XDG_CONFIG_HOME/claws-supply/auth.json`)
30+
31+
### `logout`
32+
33+
```bash
34+
claws-supply logout [--json]
35+
```
36+
37+
What it does:
38+
39+
- Removes local CLI auth state file (`auth.json`) if present
40+
- Succeeds even when no auth state file exists (idempotent)
41+
- Does not revoke server-side session/token (local-only logout)
42+
43+
### `build`
44+
45+
```bash
46+
claws-supply build [--source <path>] [--title <title>] [--slug <slug>] [--yes] [--include <glob>] [--exclude <glob>] [-D] [--json]
47+
```
48+
49+
What it does:
50+
51+
- Requires existing auth state (for bearer token + publisher hash)
52+
- Checks slug availability via `/api/cli/v1/templates/slug-availability`
53+
- Scans source files and lets you choose include groups (interactive default)
54+
- Builds deterministic zip with generated `manifest.json`
55+
- Stores artifact metadata under project-local `.claws-supply`
56+
57+
Build artifact output:
58+
59+
- `./.claws-supply/builds/<slug>/v1/template-v1.zip`
60+
- `./.claws-supply/builds/<slug>/v1/manifest.json`
61+
- `./.claws-supply/builds/<slug>/v1/artifact.json`
62+
- `./.claws-supply/latest-build.json`
63+
64+
Hard exclusions (always excluded):
65+
66+
- Memory: `memory/**`, `MEMORY.md`
67+
- Secrets/state: `.env*`, `credentials/**`, `auth-profiles.json`, `sessions/**`, `logs/**`, sandbox/state files
68+
- Generated/noisy dirs: `.git/**`, `node_modules/**`, `.next/**`, `.turbo/**`, `dist/**`
69+
70+
### `publish`
71+
72+
```bash
73+
claws-supply publish [--artifact <zipPath>] [-D] [--json]
74+
```
75+
76+
What it does:
77+
78+
- Loads latest build artifact (or `--artifact` path)
79+
- Requests upload token via `/api/cli/v1/templates/uploads/zip-token`
80+
- Uploads zip to deterministic private blob pathname with Vercel Blob client token
81+
- Finalizes publish via `/api/cli/v1/templates/publish`
82+
- Prints created draft template URL
83+
84+
Environment targeting:
85+
86+
- Default: production API (`https://claws.supply`)
87+
- `-D`: local API (`http://localhost:3039`)
88+
89+
## Quick Start
90+
91+
```bash
92+
cd /Users/anthonyriera/code/hourglass/packages/cli
93+
bun run build
94+
node dist/index.js auth -D
95+
node dist/index.js build -D
96+
node dist/index.js publish -D
97+
```
98+
99+
## Local Testing Before Deploy
100+
101+
Automated:
102+
103+
```bash
104+
cd /Users/anthonyriera/code/hourglass/packages/cli
105+
bun run test
106+
```
107+
108+
Manual smoke:
109+
110+
```bash
111+
cd /Users/anthonyriera/code/hourglass/packages/cli
112+
bun run smoke:local
113+
```
114+
115+
Prerequisites:
116+
117+
- Run local web API at `http://localhost:3039`
118+
- `apps/web/.env.local` includes `PRIVATE_READ_WRITE_TOKEN`
119+
- Approve device code in browser during `auth`
120+
121+
## Troubleshooting
122+
123+
| Symptom | Likely Cause | Resolution |
124+
| --- | --- | --- |
125+
| `401 Unauthorized` | Expired/invalid token | Re-run `claws-supply auth` |
126+
| `409 slug already in use` | Slug conflict | Pick another slug and rebuild |
127+
| `422` publish validation error | Manifest/title/slug mismatch or artifact integrity issue | Re-run `build`, then `publish` |
128+
| `429 Too many requests` | API rate limit | Wait and retry |
129+
| Upload token errors | Missing web env config | Ensure `PRIVATE_READ_WRITE_TOKEN` is set for `apps/web` |
130+
131+
## Scripts
132+
133+
- `bun run build` — compile CLI with tsup
134+
- `bun run test` — run vitest suite
135+
- `bun run test:watch` — vitest watch mode
136+
- `bun run smoke:local` — guided local smoke checklist

packages/cli/package.json

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
{
2+
"name": "@claws-supply/cli",
3+
"version": "0.1.0",
4+
"private": true,
5+
"type": "module",
6+
"bin": {
7+
"claws-supply": "./dist/index.js"
8+
},
9+
"files": [
10+
"dist"
11+
],
12+
"engines": {
13+
"node": ">=20"
14+
},
15+
"scripts": {
16+
"build": "tsup",
17+
"test": "vitest run",
18+
"test:watch": "vitest",
19+
"smoke:local": "node ./scripts/smoke-local.mjs"
20+
},
21+
"dependencies": {
22+
"@vercel/blob": "^2.3.0",
23+
"commander": "^14.0.0",
24+
"fast-glob": "^3.3.3",
25+
"fflate": "^0.8.2",
26+
"fs-extra": "^11.3.2",
27+
"kleur": "^4.1.5",
28+
"ora": "^8.2.0",
29+
"picomatch": "^4.0.3",
30+
"prompts": "^2.4.2",
31+
"zod": "^4.3.6"
32+
},
33+
"devDependencies": {
34+
"@types/fs-extra": "^11.0.4",
35+
"@types/node": "^20.19.13",
36+
"@types/prompts": "^2.4.9",
37+
"tsup": "^8.5.0",
38+
"typescript": "^5.9.2",
39+
"vitest": "^3.2.4"
40+
}
41+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
#!/usr/bin/env node
2+
3+
console.log("claws-supply local smoke guide");
4+
console.log("");
5+
console.log("1) Start web API locally in another terminal:");
6+
console.log(" cd /Users/anthonyriera/code/hourglass/apps/web && bun run dev");
7+
console.log("");
8+
console.log("2) In this package, build the CLI:");
9+
console.log(" cd /Users/anthonyriera/code/hourglass/packages/cli && bun run build");
10+
console.log("");
11+
console.log("3) Run auth (opens Better Auth device URL):");
12+
console.log(" node dist/index.js auth -D");
13+
console.log("");
14+
console.log("4) Build from a template workspace:");
15+
console.log(" node dist/index.js build -D");
16+
console.log("");
17+
console.log("5) Publish latest build:");
18+
console.log(" node dist/index.js publish -D");
19+
console.log("");
20+
console.log("Requirements:");
21+
console.log("- apps/web/.env.local must include PRIVATE_READ_WRITE_TOKEN for upload-token issuance.");
22+
console.log("- You must sign in and approve the device code in browser during step 3.");

packages/cli/src/cli.ts

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
import { Command } from "commander";
2+
import { runAuthCommand, type AuthCommandOptions } from "./commands/auth";
3+
import { runBuildCommand, type BuildCommandOptions } from "./commands/build";
4+
import { runLogoutCommand, type LogoutCommandOptions } from "./commands/logout";
5+
import { runPublishCommand, type PublishCommandOptions } from "./commands/publish";
6+
import { DEFAULT_CLIENT_ID } from "./lib/constants";
7+
import { EXIT_CODES, normalizeError } from "./utils/errors";
8+
import { createLogger, formatErrorMessage } from "./utils/logger";
9+
10+
function collectString(value: string, previous: string[]): string[] {
11+
return [...previous, value];
12+
}
13+
14+
function toHint(error: ReturnType<typeof normalizeError>): string | undefined {
15+
if (error.status === 401) {
16+
return "Run `claws-supply auth` again and ensure your session is still valid.";
17+
}
18+
19+
if (error.status === 409) {
20+
return "Pick a different slug and run `claws-supply build` again.";
21+
}
22+
23+
if (error.status === 422) {
24+
return "Rebuild the artifact and verify manifest/title/slug integrity.";
25+
}
26+
27+
if (error.status === 429) {
28+
return "Rate limit reached. Wait briefly and retry.";
29+
}
30+
31+
return undefined;
32+
}
33+
34+
function wrapCommand<T extends { json?: boolean }>(handler: (options: T) => Promise<void>) {
35+
return async (options: T) => {
36+
try {
37+
await handler(options);
38+
} catch (error) {
39+
const normalized = normalizeError(error);
40+
const logger = createLogger({ json: options.json ?? false });
41+
const hint = toHint(normalized);
42+
43+
if (normalized.exitCode === EXIT_CODES.SUCCESS) {
44+
if (options.json) {
45+
logger.json({
46+
success: false,
47+
canceled: true,
48+
message: normalized.message,
49+
exitCode: normalized.exitCode,
50+
});
51+
} else {
52+
logger.warn(normalized.message);
53+
}
54+
process.exitCode = normalized.exitCode;
55+
return;
56+
}
57+
58+
if (options.json) {
59+
logger.json({
60+
success: false,
61+
error: {
62+
message: normalized.message,
63+
code: normalized.code,
64+
status: normalized.status,
65+
exitCode: normalized.exitCode,
66+
hint,
67+
},
68+
});
69+
} else {
70+
logger.error(formatErrorMessage(normalized.message, hint));
71+
}
72+
73+
process.exitCode = normalized.exitCode;
74+
}
75+
};
76+
}
77+
78+
export function createProgram(): Command {
79+
const program = new Command();
80+
81+
program
82+
.name("claws-supply")
83+
.description("claws.supply template creator CLI")
84+
.showHelpAfterError(true);
85+
86+
program
87+
.command("auth")
88+
.description("Authenticate via Better Auth device authorization")
89+
.option("-D, --dev", "Use local API at http://localhost:3039")
90+
.option("--client-id <id>", "Device auth client ID", DEFAULT_CLIENT_ID)
91+
.option("--no-open", "Do not auto-open browser")
92+
.option("--json", "Emit machine-readable JSON output")
93+
.action(wrapCommand<AuthCommandOptions>(runAuthCommand));
94+
95+
program
96+
.command("logout")
97+
.description("Clear local CLI auth state")
98+
.option("--json", "Emit machine-readable JSON output")
99+
.action(wrapCommand<LogoutCommandOptions>(runLogoutCommand));
100+
101+
program
102+
.command("build")
103+
.description("Build and sign a local template artifact")
104+
.option("-D, --dev", "Use local API at http://localhost:3039")
105+
.option("--source <path>", "Source directory", process.cwd())
106+
.option("--title <title>", "Template title")
107+
.option("--slug <slug>", "Template slug")
108+
.option("--include <glob>", "Additional include pattern", collectString, [])
109+
.option("--exclude <glob>", "Additional exclude pattern", collectString, [])
110+
.option("--yes", "Use defaults and skip interactive prompts")
111+
.option("--json", "Emit machine-readable JSON output")
112+
.action(wrapCommand<BuildCommandOptions>(runBuildCommand));
113+
114+
program
115+
.command("publish")
116+
.description("Upload and publish the latest build artifact as draft")
117+
.option("-D, --dev", "Use local API at http://localhost:3039")
118+
.option("--artifact <path>", "Path to a built zip artifact")
119+
.option("--json", "Emit machine-readable JSON output")
120+
.action(wrapCommand<PublishCommandOptions>(runPublishCommand));
121+
122+
return program;
123+
}
124+
125+
export async function runCli(argv = process.argv): Promise<void> {
126+
const program = createProgram();
127+
await program.parseAsync(argv);
128+
}

0 commit comments

Comments
 (0)