@shardflux/sdk 0.7.0 → 0.9.0

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,99 @@
1
+ /** `^[A-Za-z0-9][A-Za-z0-9._:-]{7,127}$` (cell-api.yaml ExecutionIdValue). */
2
+ export const EXECUTION_ID = /^[A-Za-z0-9][A-Za-z0-9._:-]{7,127}$/;
3
+ /** A fresh execution id: `ex-` plus a random UUID (39 characters, valid per EXECUTION_ID). */
4
+ export function newExecutionId() {
5
+ return `ex-${crypto.randomUUID()}`;
6
+ }
7
+ const bytes = (s) => (s ? new Uint8Array(Buffer.from(s, 'base64')) : new Uint8Array());
8
+ /** The outcome of an execution: decoded output, exit status, the revision it produced and what it changed. */
9
+ export class ExecutionResult {
10
+ executionId;
11
+ state;
12
+ /** The tree revision the command ran on. */
13
+ baseRevision;
14
+ /**
15
+ * The tree revision after the execution: `baseRevision + 1` when it changed files, else `baseRevision`; null unless
16
+ * `state` is `succeeded` (a failed or lost execution publishes nothing).
17
+ */
18
+ treeRevision;
19
+ /** The command's exit status (-1 when a signal ended it); null unless succeeded. */
20
+ exitCode;
21
+ termSignal;
22
+ /** The command ran past `timeoutMs` and was killed. */
23
+ timedOut;
24
+ /** Standard output, at most `outputLimitBytes`. */
25
+ stdout;
26
+ stderr;
27
+ stdoutTruncated;
28
+ stderrTruncated;
29
+ /** What the execution changed under /home/user, sorted by path (at most 10 000; see `changedTruncated`). */
30
+ changed;
31
+ changedTruncated;
32
+ /** Milliseconds and counts: queue_ms, run_ms, publish_ms, total_ms and the host's (boot_ms, exec_ms, vm_seconds, ...). */
33
+ timings;
34
+ /** failed / lost: why (`details.reason`); null otherwise. */
35
+ error;
36
+ createdAt;
37
+ finishedAt;
38
+ /**
39
+ * The call that returned this result did not start the execution: `executions.run()` answered 200 (the execution
40
+ * already existed: its recorded result, or the running one waited for), or it came from `executions.get()`. False
41
+ * only for the run() call whose request started it (201).
42
+ */
43
+ replayed;
44
+ /** The body as the cell sent it. */
45
+ raw;
46
+ constructor(body, replayed) {
47
+ this.raw = body;
48
+ this.executionId = body.execution_id;
49
+ this.state = body.state;
50
+ this.baseRevision = body.base_revision;
51
+ this.treeRevision = body.tree_revision ?? null;
52
+ this.exitCode = body.exit_code ?? null;
53
+ this.termSignal = body.term_signal ?? null;
54
+ this.timedOut = body.timed_out === true;
55
+ this.stdout = bytes(body.stdout);
56
+ this.stderr = bytes(body.stderr);
57
+ this.stdoutTruncated = body.stdout_truncated === true;
58
+ this.stderrTruncated = body.stderr_truncated === true;
59
+ this.changed = body.changed ?? [];
60
+ this.changedTruncated = body.changed_truncated === true;
61
+ this.timings = body.timings ?? {};
62
+ const e = body.error;
63
+ this.error =
64
+ e && typeof e === 'object' && typeof e.code === 'string'
65
+ ? {
66
+ code: e.code,
67
+ message: typeof e.message === 'string' ? e.message : '',
68
+ retryable: e.retryable === true,
69
+ ...(typeof e.details === 'object' && e.details !== null ? { details: e.details } : {}),
70
+ }
71
+ : null;
72
+ this.createdAt = body.created_at;
73
+ this.finishedAt = body.finished_at ?? null;
74
+ this.replayed = replayed;
75
+ }
76
+ /** Still queued or running (only `executions.get()` returns such a result). */
77
+ get pending() {
78
+ return this.state === 'queued' || this.state === 'running';
79
+ }
80
+ /** The command ran to its end with exit code 0. */
81
+ get ok() {
82
+ return this.state === 'succeeded' && this.exitCode === 0;
83
+ }
84
+ /** A stream as text (UTF-8; invalid sequences become U+FFFD). */
85
+ text(stream = 'stdout') {
86
+ return new TextDecoder().decode(stream === 'stderr' ? this.stderr : this.stdout);
87
+ }
88
+ get stdoutText() {
89
+ return this.text('stdout');
90
+ }
91
+ get stderrText() {
92
+ return this.text('stderr');
93
+ }
94
+ /** `error.details.reason` of a failed or lost execution (null otherwise). */
95
+ get errorReason() {
96
+ const r = this.error?.details?.reason;
97
+ return typeof r === 'string' ? r : null;
98
+ }
99
+ }
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Product feedback (POST /v1/feedback, 0.9.0+): a short message from the customer or their coding agent that goes
3
+ * straight to the Shardflux founder by email.
4
+ *
5
+ * await cloud.sendFeedback({ message: 'open took 40 s on python-node-browser', category: 'bug', context: { requestId: err.requestId } });
6
+ *
7
+ * Any valid API key may send (no tool permission needed). The server bounds it per key (429 `rate_limited`,
8
+ * `retryAfterSeconds` on the error; the SDK never retries it) and folds a repeat of the same message within 24 hours
9
+ * into the first one (`duplicate: true`, no second email). An empty or over-long message, an unknown category or an
10
+ * over-long context field is 422 `validation_failed`. Anything shaped like an API key is redacted server side.
11
+ *
12
+ * The request and response types are written by hand until the generated contract (src/generated/app-api.ts) has the
13
+ * route; type-checks.ts then pins them to it.
14
+ */
15
+ import type { ClientContext } from './client.js';
16
+ /**
17
+ * bug: something failed or behaved wrongly. confusing: an error, doc, name or output was unclear or misleading.
18
+ * missing: a capability, option or template you needed does not exist. idea: a suggestion or improvement.
19
+ * praise: something worked well. other (the server's default).
20
+ */
21
+ export type FeedbackCategory = 'bug' | 'confusing' | 'missing' | 'idea' | 'praise' | 'other';
22
+ /** Every category, in the order the help texts list them. */
23
+ export declare const FEEDBACK_CATEGORIES: readonly FeedbackCategory[];
24
+ /** Longest message the API accepts, in characters after trimming (it must also contain a non-whitespace character). */
25
+ export declare const FEEDBACK_MESSAGE_MAX_LENGTH = 8000;
26
+ /** What the feedback is about, so the founder can find the logs. Every field is optional text. */
27
+ export interface FeedbackContext {
28
+ /** Who is reporting: e.g. `claude-code`, `codex`, `cursor`, or a person (<= 100 characters). */
29
+ agent?: string;
30
+ /** Client and version (<= 200). Default: `shardflux-sdk-ts/<SDK_VERSION>`. */
31
+ client?: string;
32
+ /** Workspace id or key the feedback is about (<= 200). */
33
+ workspace?: string;
34
+ /** `requestId` of the ShardfluxApiError that prompted it (<= 200). */
35
+ requestId?: string;
36
+ /** The API error code seen, e.g. `capacity_unavailable` (<= 100). */
37
+ errorCode?: string;
38
+ /** The command, SDK call or tool call that led to it (<= 2000). */
39
+ command?: string;
40
+ /** Dashboard route (<= 300); browser clients only in practice. */
41
+ page?: string;
42
+ }
43
+ export interface SendFeedbackParams {
44
+ /** 1-8000 characters after trimming. Short and specific: what you did, what happened, what you expected. */
45
+ message: string;
46
+ /** Default (server side) `other`. */
47
+ category?: FeedbackCategory;
48
+ context?: FeedbackContext;
49
+ }
50
+ /**
51
+ * Feedback sent with a CLI user session (`ShardfluxAccount.sendFeedback`, 0.9.0+): as the signed-in user, optionally
52
+ * about one of their organizations. A project API key cannot name an organization (its own is used).
53
+ */
54
+ export interface AccountFeedbackParams extends SendFeedbackParams {
55
+ /** One of the signed-in user's organizations (404 `not_found` otherwise). */
56
+ organizationId?: string;
57
+ }
58
+ /** The stored feedback. */
59
+ export interface FeedbackReceipt {
60
+ id: string;
61
+ /** RFC 3339. For a duplicate, when the original was received. */
62
+ receivedAt: string;
63
+ /** The same message from the same key within 24 hours: the original is returned and no second email is sent. */
64
+ duplicate: boolean;
65
+ }
66
+ /** POST /v1/feedback with the context's credential (API key or CLI session). 201 (new) and 200 (duplicate) carry the same body. */
67
+ export declare function sendFeedback(ctx: ClientContext, params: AccountFeedbackParams): Promise<FeedbackReceipt>;
@@ -0,0 +1,39 @@
1
+ import { ShardfluxProtocolError } from "./errors.js";
2
+ import { SDK_VERSION } from "./http.js";
3
+ /** Every category, in the order the help texts list them. */
4
+ export const FEEDBACK_CATEGORIES = ['bug', 'confusing', 'missing', 'idea', 'praise', 'other'];
5
+ /** Longest message the API accepts, in characters after trimming (it must also contain a non-whitespace character). */
6
+ export const FEEDBACK_MESSAGE_MAX_LENGTH = 8000;
7
+ const CONTEXT_FIELDS = [
8
+ ['agent', 'agent'],
9
+ ['client', 'client'],
10
+ ['workspace', 'workspace'],
11
+ ['requestId', 'request_id'],
12
+ ['errorCode', 'error_code'],
13
+ ['command', 'command'],
14
+ ['page', 'page'],
15
+ ];
16
+ /** The request body: context fields in snake_case, `client` defaulted to this SDK. */
17
+ function feedbackBody(params) {
18
+ const context = {};
19
+ for (const [from, to] of CONTEXT_FIELDS) {
20
+ const v = params.context?.[from];
21
+ if (v !== undefined)
22
+ context[to] = v;
23
+ }
24
+ context.client ??= `shardflux-sdk-ts/${SDK_VERSION}`;
25
+ return {
26
+ message: params.message,
27
+ ...(params.category !== undefined ? { category: params.category } : {}),
28
+ ...(params.organizationId !== undefined ? { organization_id: params.organizationId } : {}),
29
+ context,
30
+ };
31
+ }
32
+ /** POST /v1/feedback with the context's credential (API key or CLI session). 201 (new) and 200 (duplicate) carry the same body. */
33
+ export async function sendFeedback(ctx, params) {
34
+ const { status, body } = await ctx.http.jsonWithStatus('POST', '/v1/feedback', { json: feedbackBody(params) }, ctx.authorization);
35
+ if (typeof body?.id !== 'string' || typeof body.received_at !== 'string' || typeof body.duplicate !== 'boolean') {
36
+ throw new ShardfluxProtocolError('POST /v1/feedback: the response is not {id, received_at, duplicate}', status);
37
+ }
38
+ return { id: body.id, receivedAt: body.received_at, duplicate: body.duplicate };
39
+ }