@solcreek/cli 0.4.10 → 0.4.11

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.
@@ -0,0 +1,75 @@
1
+ /**
2
+ * `creek logs` — read structured tenant logs from R2 archive.
3
+ *
4
+ * Auth: requires `creek login`. Server is responsible for tenant
5
+ * isolation — the CLI simply targets the project slug, the
6
+ * authenticated session decides which team's logs are visible.
7
+ *
8
+ * Project resolution: --project flag wins; otherwise resolve from
9
+ * cwd creek.toml / wrangler.*. If neither, error with hint.
10
+ *
11
+ * `--follow` is reserved for Step 7 (WebSocket subscribe). For now
12
+ * the command is one-shot historical query.
13
+ *
14
+ * Output:
15
+ * default → human-friendly multi-line per entry, colored by
16
+ * outcome (ok=dim, exception=red, etc.)
17
+ * --json → newline-delimited LogEntry JSON, suitable for `| jq`
18
+ */
19
+ export declare const logsCommand: import("citty").CommandDef<{
20
+ json: {
21
+ type: "boolean";
22
+ description: string;
23
+ default: boolean;
24
+ };
25
+ yes: {
26
+ type: "boolean";
27
+ description: string;
28
+ default: boolean;
29
+ };
30
+ project: {
31
+ type: "string";
32
+ description: string;
33
+ };
34
+ since: {
35
+ type: "string";
36
+ description: string;
37
+ };
38
+ until: {
39
+ type: "string";
40
+ description: string;
41
+ };
42
+ outcome: {
43
+ type: "string";
44
+ description: string;
45
+ };
46
+ "script-type": {
47
+ type: "string";
48
+ description: string;
49
+ };
50
+ deployment: {
51
+ type: "string";
52
+ description: string;
53
+ };
54
+ branch: {
55
+ type: "string";
56
+ description: string;
57
+ };
58
+ level: {
59
+ type: "string";
60
+ description: string;
61
+ };
62
+ search: {
63
+ type: "string";
64
+ description: string;
65
+ };
66
+ limit: {
67
+ type: "string";
68
+ description: string;
69
+ };
70
+ follow: {
71
+ type: "boolean";
72
+ description: string;
73
+ };
74
+ }>;
75
+ //# sourceMappingURL=logs.d.ts.map
@@ -0,0 +1,233 @@
1
+ import { defineCommand } from "citty";
2
+ import consola from "consola";
3
+ import { CreekClient, resolveConfig, ConfigNotFoundError, } from "@solcreek/sdk";
4
+ import { getToken, getApiUrl } from "../utils/config.js";
5
+ import { globalArgs, resolveJsonMode, jsonOutput, AUTH_BREADCRUMBS, NO_PROJECT_BREADCRUMBS, } from "../utils/output.js";
6
+ /**
7
+ * `creek logs` — read structured tenant logs from R2 archive.
8
+ *
9
+ * Auth: requires `creek login`. Server is responsible for tenant
10
+ * isolation — the CLI simply targets the project slug, the
11
+ * authenticated session decides which team's logs are visible.
12
+ *
13
+ * Project resolution: --project flag wins; otherwise resolve from
14
+ * cwd creek.toml / wrangler.*. If neither, error with hint.
15
+ *
16
+ * `--follow` is reserved for Step 7 (WebSocket subscribe). For now
17
+ * the command is one-shot historical query.
18
+ *
19
+ * Output:
20
+ * default → human-friendly multi-line per entry, colored by
21
+ * outcome (ok=dim, exception=red, etc.)
22
+ * --json → newline-delimited LogEntry JSON, suitable for `| jq`
23
+ */
24
+ export const logsCommand = defineCommand({
25
+ meta: {
26
+ name: "logs",
27
+ description: "Read recent log entries for a project",
28
+ },
29
+ args: {
30
+ project: {
31
+ type: "string",
32
+ description: "Project slug. Defaults to creek.toml in cwd.",
33
+ },
34
+ since: {
35
+ type: "string",
36
+ description: "Time window start. Relative (1h, 30m, 2d) or ISO. Default: 1h",
37
+ },
38
+ until: {
39
+ type: "string",
40
+ description: 'Time window end. "now" or ISO. Default: now',
41
+ },
42
+ outcome: {
43
+ type: "string",
44
+ description: "Filter by tail outcome. Repeatable via comma (ok,exception).",
45
+ },
46
+ "script-type": {
47
+ type: "string",
48
+ description: "Filter by production/branch/deployment. Repeatable via comma.",
49
+ },
50
+ deployment: {
51
+ type: "string",
52
+ description: "8-hex deploy id — scopes to that single deployment preview.",
53
+ },
54
+ branch: {
55
+ type: "string",
56
+ description: "Branch name — scopes to that branch preview.",
57
+ },
58
+ level: {
59
+ type: "string",
60
+ description: "Filter by console level (error,warn,...). Entry needs at least one matching log line.",
61
+ },
62
+ search: {
63
+ type: "string",
64
+ description: "Substring match against console messages, exceptions, and request URLs.",
65
+ },
66
+ limit: {
67
+ type: "string",
68
+ description: "Max entries to print. Default 100, max 1000.",
69
+ },
70
+ follow: {
71
+ type: "boolean",
72
+ description: "(Step 7 — not yet implemented) Live tail via WebSocket.",
73
+ },
74
+ ...globalArgs,
75
+ },
76
+ async run({ args }) {
77
+ if (args.follow) {
78
+ consola.warn("--follow is not yet implemented (Phase 8 Step 7).");
79
+ consola.info("This command currently returns historical entries only.");
80
+ }
81
+ const jsonMode = resolveJsonMode(args);
82
+ const token = getToken();
83
+ if (!token) {
84
+ if (jsonMode)
85
+ jsonOutput({ ok: false, error: "not_authenticated" }, 1, AUTH_BREADCRUMBS);
86
+ consola.error("Not authenticated. Run `creek login` first.");
87
+ process.exit(1);
88
+ }
89
+ const projectSlug = await resolveProjectSlug(args.project, jsonMode);
90
+ const client = new CreekClient(getApiUrl(), token);
91
+ const filters = {
92
+ ...(args.since ? { since: args.since } : {}),
93
+ ...(args.until ? { until: args.until } : {}),
94
+ ...(args.deployment ? { deployment: args.deployment } : {}),
95
+ ...(args.branch ? { branch: args.branch } : {}),
96
+ ...(args.search ? { search: args.search } : {}),
97
+ ...(args.limit ? { limit: Number(args.limit) } : {}),
98
+ ...(args.outcome
99
+ ? { outcomes: parseList(args.outcome) }
100
+ : {}),
101
+ ...(args["script-type"]
102
+ ? {
103
+ scriptTypes: parseList(args["script-type"]),
104
+ }
105
+ : {}),
106
+ ...(args.level
107
+ ? { levels: parseList(args.level) }
108
+ : {}),
109
+ };
110
+ let response;
111
+ try {
112
+ response = await client.getLogs(projectSlug, filters);
113
+ }
114
+ catch (err) {
115
+ const msg = err instanceof Error ? err.message : String(err);
116
+ if (jsonMode)
117
+ jsonOutput({ ok: false, error: "logs_failed", message: msg }, 1, []);
118
+ consola.error(`Failed to read logs: ${msg}`);
119
+ process.exit(1);
120
+ }
121
+ if (jsonMode) {
122
+ // ndjson — easy to pipe to jq
123
+ for (const entry of response.entries) {
124
+ process.stdout.write(JSON.stringify(entry) + "\n");
125
+ }
126
+ if (response.truncated) {
127
+ process.stderr.write(`# truncated — more entries match. Refine --since/--limit to narrow.\n`);
128
+ }
129
+ return;
130
+ }
131
+ if (response.entries.length === 0) {
132
+ consola.info("No log entries match the query.");
133
+ return;
134
+ }
135
+ // Human output: oldest at top so the latest entry is closest to the prompt.
136
+ const ordered = [...response.entries].reverse();
137
+ for (const entry of ordered) {
138
+ printEntry(entry);
139
+ }
140
+ if (response.truncated) {
141
+ consola.warn(`Truncated to ${response.entries.length} entries — refine --since/--limit to see more.`);
142
+ }
143
+ },
144
+ });
145
+ async function resolveProjectSlug(override, jsonMode) {
146
+ if (override)
147
+ return override;
148
+ let resolved;
149
+ try {
150
+ resolved = resolveConfig(process.cwd());
151
+ }
152
+ catch (err) {
153
+ if (err instanceof ConfigNotFoundError) {
154
+ if (jsonMode)
155
+ jsonOutput({ ok: false, error: "no_project", message: "No project config in cwd" }, 1, NO_PROJECT_BREADCRUMBS);
156
+ consola.error("No project config in cwd. Pass --project <slug>.");
157
+ process.exit(1);
158
+ }
159
+ throw err;
160
+ }
161
+ return resolved.projectName;
162
+ }
163
+ function parseList(input) {
164
+ return input
165
+ .split(",")
166
+ .map((s) => s.trim())
167
+ .filter(Boolean);
168
+ }
169
+ const COLOR = {
170
+ reset: "\x1b[0m",
171
+ dim: "\x1b[2m",
172
+ red: "\x1b[31m",
173
+ yellow: "\x1b[33m",
174
+ green: "\x1b[32m",
175
+ cyan: "\x1b[36m",
176
+ gray: "\x1b[90m",
177
+ };
178
+ function color(s, c) {
179
+ return process.stdout.isTTY ? `${COLOR[c]}${s}${COLOR.reset}` : s;
180
+ }
181
+ function printEntry(entry) {
182
+ const ts = new Date(entry.timestamp).toISOString().replace("T", " ").slice(0, 19);
183
+ const outcomeColor = entry.outcome === "ok"
184
+ ? "gray"
185
+ : entry.outcome === "exception"
186
+ ? "red"
187
+ : "yellow";
188
+ const status = entry.request?.status;
189
+ const statusStr = status === undefined
190
+ ? ""
191
+ : status >= 500
192
+ ? color(String(status), "red")
193
+ : status >= 400
194
+ ? color(String(status), "yellow")
195
+ : color(String(status), "green");
196
+ const variant = entry.scriptType === "production"
197
+ ? ""
198
+ : entry.scriptType === "branch"
199
+ ? ` [branch ${entry.branch}]`
200
+ : ` [deploy ${entry.deployId}]`;
201
+ const headline = [
202
+ color(ts, "dim"),
203
+ color(entry.outcome, outcomeColor),
204
+ entry.request?.method ?? "—",
205
+ entry.request?.url
206
+ ? new URL(entry.request.url).pathname + new URL(entry.request.url).search
207
+ : "—",
208
+ statusStr,
209
+ color(variant, "dim"),
210
+ ]
211
+ .filter(Boolean)
212
+ .join(" ");
213
+ process.stdout.write(headline + "\n");
214
+ for (const log of entry.logs) {
215
+ const levelColor = log.level === "error" ? "red" : log.level === "warn" ? "yellow" : "cyan";
216
+ const msg = log.message
217
+ .map((m) => (typeof m === "string" ? m : safeStringify(m)))
218
+ .join(" ");
219
+ process.stdout.write(` ${color(log.level.padEnd(5), levelColor)} ${msg}\n`);
220
+ }
221
+ for (const ex of entry.exceptions) {
222
+ process.stdout.write(` ${color("exc", "red")} ${color(ex.name, "red")}: ${ex.message}\n`);
223
+ }
224
+ }
225
+ function safeStringify(v) {
226
+ try {
227
+ return JSON.stringify(v);
228
+ }
229
+ catch {
230
+ return String(v);
231
+ }
232
+ }
233
+ //# sourceMappingURL=logs.js.map
package/dist/index.js CHANGED
@@ -18,6 +18,7 @@ import { devCommand } from "./commands/dev.js";
18
18
  import { rollbackCommand } from "./commands/rollback.js";
19
19
  import { opsCommand } from "./commands/ops.js";
20
20
  import { queueCommand } from "./commands/queue.js";
21
+ import { logsCommand } from "./commands/logs.js";
21
22
  const __dirname = dirname(fileURLToPath(import.meta.url));
22
23
  const cliPkg = JSON.parse(readFileSync(join(__dirname, "..", "package.json"), "utf-8"));
23
24
  // Read version from the "creek" facade package (what users install),
@@ -43,6 +44,7 @@ const main = defineCommand({
43
44
  status: statusCommand,
44
45
  projects: projectsCommand,
45
46
  deployments: deploymentsCommand,
47
+ logs: logsCommand,
46
48
  login: loginCommand,
47
49
  whoami: whoamiCommand,
48
50
  init: initCommand,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@solcreek/cli",
3
- "version": "0.4.10",
3
+ "version": "0.4.11",
4
4
  "description": "CLI for the Creek deployment platform",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -33,7 +33,7 @@
33
33
  "esbuild": "^0.25.0",
34
34
  "smol-toml": "^1.3.1",
35
35
  "ws": "^8.20.0",
36
- "@solcreek/sdk": "0.4.4"
36
+ "@solcreek/sdk": "0.4.5"
37
37
  },
38
38
  "devDependencies": {
39
39
  "@testing-library/dom": "^10.4.1",