@pippit-dev/cli 1.0.17 → 1.0.20
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 +41 -2
- package/checksums.txt +6 -6
- package/cmd/auth/auth.go +136 -142
- package/cmd/auth/auth_test.go +134 -0
- package/cmd/canvas/canvas.go +241 -0
- package/cmd/canvas/canvas_test.go +182 -0
- package/cmd/get_credit_balance.go +37 -0
- package/cmd/get_credit_balance_test.go +73 -0
- package/cmd/root.go +31 -5
- package/cmd/root_test.go +69 -0
- package/cmd/short_drama_test.go +5 -7
- package/cmd/update/update.go +34 -5
- package/cmd/update/update_test.go +84 -0
- package/dist/checksums.txt +6 -0
- package/dist/xyq-canvas-command-runtime.cjs +16 -0
- package/dist/xyq-canvas-command-runtime.cjs.LEGAL.txt +599 -0
- package/dist/xyq-canvas-command-runtime.cjs.sha256 +2 -0
- package/internal/auth/auth_test.go +813 -0
- package/internal/auth/browser_darwin.go +14 -0
- package/internal/auth/browser_env.go +35 -0
- package/internal/auth/browser_linux.go +14 -0
- package/internal/auth/browser_windows.go +14 -0
- package/internal/auth/identity.go +89 -0
- package/internal/auth/loopback.go +324 -0
- package/internal/auth/manager.go +338 -144
- package/internal/auth/store.go +303 -0
- package/internal/auth/store_file_unix.go +176 -0
- package/internal/auth/store_file_windows.go +11 -0
- package/internal/auth/types.go +72 -0
- package/internal/canvas/allocate.go +70 -0
- package/internal/canvas/apply.go +279 -0
- package/internal/canvas/canvas_test.go +559 -0
- package/internal/canvas/create.go +380 -0
- package/internal/canvas/get.go +156 -0
- package/internal/canvas/types.go +68 -0
- package/internal/canvas/upload.go +250 -0
- package/internal/common/access_key.go +42 -6
- package/internal/common/access_key_test.go +113 -0
- package/internal/common/client.go +119 -21
- package/internal/common/client_test.go +213 -0
- package/internal/common/get_credit_balance.go +61 -0
- package/internal/common/get_credit_balance_test.go +75 -0
- package/internal/common/runner.go +15 -0
- package/internal/config/config.go +11 -28
- package/internal/config/config_test.go +3 -18
- package/package.json +9 -2
- package/scripts/canvas-command.js +881 -0
- package/scripts/run.js +21 -4
- package/skills/short-drama/SKILL.md +4 -4
- package/skills/xyq-nest-skill/SKILL.md +11 -1
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
package canvas
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"context"
|
|
5
|
+
"encoding/json"
|
|
6
|
+
"fmt"
|
|
7
|
+
"regexp"
|
|
8
|
+
"strings"
|
|
9
|
+
|
|
10
|
+
"github.com/Pippit-dev/pippit-cli/internal/common"
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
var decimalIDPattern = regexp.MustCompile(`^[1-9][0-9]*$`)
|
|
14
|
+
|
|
15
|
+
type ApplyOptions struct {
|
|
16
|
+
ProjectID string
|
|
17
|
+
Request ApplyRequest
|
|
18
|
+
AllowNonAcknowledgedResults bool
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
type ApplyRequest struct {
|
|
22
|
+
BatchID string `json:"batch_id"`
|
|
23
|
+
ClientID string `json:"client_id"`
|
|
24
|
+
RootPippitAssetID string `json:"root_pippit_asset_id,omitempty"`
|
|
25
|
+
Transactions []PatchTransaction `json:"transactions"`
|
|
26
|
+
Base map[string]any `json:"Base"`
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
type PatchTransaction struct {
|
|
30
|
+
TransactionID string `json:"transaction_id"`
|
|
31
|
+
Intent string `json:"intent,omitempty"`
|
|
32
|
+
MergeKey string `json:"merge_key,omitempty"`
|
|
33
|
+
Attempt *int32 `json:"attempt,omitempty"`
|
|
34
|
+
EnqueuedAt *int64 `json:"enqueued_at,omitempty"`
|
|
35
|
+
Patches []PatchEntry `json:"patches"`
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
type PatchEntry struct {
|
|
39
|
+
AssetID string `json:"asset_id"`
|
|
40
|
+
BaseAssetVersion *int64 `json:"base_asset_version,omitempty"`
|
|
41
|
+
AssetSourceType *int32 `json:"asset_source_type,omitempty"`
|
|
42
|
+
Op string `json:"op"`
|
|
43
|
+
Path string `json:"path"`
|
|
44
|
+
Value json.RawMessage `json:"value,omitempty"`
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
type ApplyResult struct {
|
|
48
|
+
BatchID string `json:"batch_id"`
|
|
49
|
+
Results []PatchTransactionResult `json:"results"`
|
|
50
|
+
LogID string `json:"log_id,omitempty"`
|
|
51
|
+
|
|
52
|
+
rawResults []json.RawMessage
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
type PatchTransactionResult struct {
|
|
56
|
+
TransactionID string `json:"transaction_id"`
|
|
57
|
+
Status string `json:"status"`
|
|
58
|
+
AssetVersions map[string]int64 `json:"asset_versions,omitempty"`
|
|
59
|
+
BlockedByTransaction string `json:"blocked_by,omitempty"`
|
|
60
|
+
Error string `json:"error,omitempty"`
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
type applyData struct {
|
|
64
|
+
Results []json.RawMessage `json:"results"`
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
type decodedPatchTransactionResult struct {
|
|
68
|
+
result PatchTransactionResult
|
|
69
|
+
raw json.RawMessage
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
func (result ApplyResult) MarshalJSON() ([]byte, error) {
|
|
73
|
+
type applyResultJSON struct {
|
|
74
|
+
BatchID string `json:"batch_id"`
|
|
75
|
+
Results any `json:"results"`
|
|
76
|
+
LogID string `json:"log_id,omitempty"`
|
|
77
|
+
}
|
|
78
|
+
results := any(result.Results)
|
|
79
|
+
if len(result.rawResults) > 0 {
|
|
80
|
+
results = result.rawResults
|
|
81
|
+
}
|
|
82
|
+
return json.Marshal(applyResultJSON{
|
|
83
|
+
BatchID: result.BatchID,
|
|
84
|
+
Results: results,
|
|
85
|
+
LogID: result.LogID,
|
|
86
|
+
})
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
func Apply(ctx context.Context, opts ApplyOptions, runner *common.Runner) (*ApplyResult, error) {
|
|
90
|
+
client, err := runnerClient(runner, "canvas apply")
|
|
91
|
+
if err != nil {
|
|
92
|
+
return nil, err
|
|
93
|
+
}
|
|
94
|
+
projectID := strings.TrimSpace(opts.ProjectID)
|
|
95
|
+
if projectID != "" && !decimalIDPattern.MatchString(projectID) {
|
|
96
|
+
return nil, fmt.Errorf("canvas apply project_id must be a positive decimal JSON string")
|
|
97
|
+
}
|
|
98
|
+
request := opts.Request
|
|
99
|
+
request.Base = base()
|
|
100
|
+
if err := validateApplyRequest(&request); err != nil {
|
|
101
|
+
return nil, err
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
var envelope responseEnvelope
|
|
105
|
+
if err := client.SendRequest(ctx, pathWithProjectID(ApplyPath, projectID), request, &envelope); err != nil {
|
|
106
|
+
return nil, fmt.Errorf("canvas apply request failed; outcome may be ambiguous, do not replay blindly: %w", err)
|
|
107
|
+
}
|
|
108
|
+
if err := envelope.validate("canvas apply"); err != nil {
|
|
109
|
+
return nil, err
|
|
110
|
+
}
|
|
111
|
+
var data applyData
|
|
112
|
+
if err := json.Unmarshal(envelope.Data, &data); err != nil {
|
|
113
|
+
return nil, common.NewLogIDError(
|
|
114
|
+
fmt.Sprintf("canvas apply returned invalid data: %v; query affected assets before retrying because outcome cannot be confirmed", err),
|
|
115
|
+
envelope.LogID,
|
|
116
|
+
)
|
|
117
|
+
}
|
|
118
|
+
decodedResults, err := decodeApplyResults(data.Results)
|
|
119
|
+
if err != nil {
|
|
120
|
+
return nil, common.NewLogIDError(
|
|
121
|
+
fmt.Sprintf("canvas apply returned invalid data: %v; query affected assets before retrying because outcome cannot be confirmed", err),
|
|
122
|
+
envelope.LogID,
|
|
123
|
+
)
|
|
124
|
+
}
|
|
125
|
+
ordered, rawResults, _, err := validateApplyResults(
|
|
126
|
+
request.Transactions,
|
|
127
|
+
decodedResults,
|
|
128
|
+
opts.AllowNonAcknowledgedResults,
|
|
129
|
+
)
|
|
130
|
+
if err != nil {
|
|
131
|
+
return nil, common.NewLogIDError(
|
|
132
|
+
fmt.Sprintf("%s; query affected assets before retrying because outcome cannot be confirmed", err),
|
|
133
|
+
envelope.LogID,
|
|
134
|
+
)
|
|
135
|
+
}
|
|
136
|
+
result := &ApplyResult{
|
|
137
|
+
BatchID: request.BatchID,
|
|
138
|
+
Results: ordered,
|
|
139
|
+
LogID: strings.TrimSpace(envelope.LogID),
|
|
140
|
+
}
|
|
141
|
+
if opts.AllowNonAcknowledgedResults {
|
|
142
|
+
result.rawResults = rawResults
|
|
143
|
+
}
|
|
144
|
+
return result, nil
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
func validateApplyRequest(request *ApplyRequest) error {
|
|
148
|
+
if request == nil {
|
|
149
|
+
return fmt.Errorf("canvas apply request is required")
|
|
150
|
+
}
|
|
151
|
+
request.BatchID = strings.TrimSpace(request.BatchID)
|
|
152
|
+
request.ClientID = strings.TrimSpace(request.ClientID)
|
|
153
|
+
request.RootPippitAssetID = strings.TrimSpace(request.RootPippitAssetID)
|
|
154
|
+
if request.BatchID == "" {
|
|
155
|
+
return fmt.Errorf("canvas apply batch_id is required")
|
|
156
|
+
}
|
|
157
|
+
if request.ClientID == "" {
|
|
158
|
+
return fmt.Errorf("canvas apply client_id is required")
|
|
159
|
+
}
|
|
160
|
+
if len(request.Transactions) != 1 {
|
|
161
|
+
return fmt.Errorf("canvas apply requires exactly one transaction in this beta; put related patches in that transaction")
|
|
162
|
+
}
|
|
163
|
+
transactionIDs := make(map[string]struct{}, len(request.Transactions))
|
|
164
|
+
for txIndex := range request.Transactions {
|
|
165
|
+
tx := &request.Transactions[txIndex]
|
|
166
|
+
tx.TransactionID = strings.TrimSpace(tx.TransactionID)
|
|
167
|
+
if tx.TransactionID == "" {
|
|
168
|
+
return fmt.Errorf("canvas apply transactions[%d].transaction_id is required", txIndex)
|
|
169
|
+
}
|
|
170
|
+
if _, duplicate := transactionIDs[tx.TransactionID]; duplicate {
|
|
171
|
+
return fmt.Errorf("canvas apply transaction_id %q is duplicated", tx.TransactionID)
|
|
172
|
+
}
|
|
173
|
+
transactionIDs[tx.TransactionID] = struct{}{}
|
|
174
|
+
if len(tx.Patches) == 0 {
|
|
175
|
+
return fmt.Errorf("canvas apply transaction %q has no patches", tx.TransactionID)
|
|
176
|
+
}
|
|
177
|
+
for patchIndex := range tx.Patches {
|
|
178
|
+
patch := &tx.Patches[patchIndex]
|
|
179
|
+
patch.AssetID = strings.TrimSpace(patch.AssetID)
|
|
180
|
+
patch.Op = strings.ToLower(strings.TrimSpace(patch.Op))
|
|
181
|
+
if patch.AssetID == "" {
|
|
182
|
+
return fmt.Errorf("canvas apply transaction %q patch[%d].asset_id is required", tx.TransactionID, patchIndex)
|
|
183
|
+
}
|
|
184
|
+
switch patch.Op {
|
|
185
|
+
case "add", "replace":
|
|
186
|
+
if len(patch.Value) == 0 || !json.Valid(patch.Value) {
|
|
187
|
+
return fmt.Errorf("canvas apply transaction %q patch[%d].value must be valid JSON", tx.TransactionID, patchIndex)
|
|
188
|
+
}
|
|
189
|
+
case "remove":
|
|
190
|
+
if len(patch.Value) != 0 && !json.Valid(patch.Value) {
|
|
191
|
+
return fmt.Errorf("canvas apply transaction %q patch[%d].value must be valid JSON", tx.TransactionID, patchIndex)
|
|
192
|
+
}
|
|
193
|
+
default:
|
|
194
|
+
return fmt.Errorf("canvas apply transaction %q patch[%d].op must be add, replace, or remove", tx.TransactionID, patchIndex)
|
|
195
|
+
}
|
|
196
|
+
if patch.BaseAssetVersion != nil && *patch.BaseAssetVersion < 0 {
|
|
197
|
+
return fmt.Errorf("canvas apply transaction %q patch[%d].base_asset_version must not be negative", tx.TransactionID, patchIndex)
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
return nil
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
func decodeApplyResults(rawResults []json.RawMessage) ([]decodedPatchTransactionResult, error) {
|
|
205
|
+
results := make([]decodedPatchTransactionResult, 0, len(rawResults))
|
|
206
|
+
for index, rawResult := range rawResults {
|
|
207
|
+
var result PatchTransactionResult
|
|
208
|
+
if err := json.Unmarshal(rawResult, &result); err != nil {
|
|
209
|
+
return nil, fmt.Errorf("decode transaction result[%d]: %w", index, err)
|
|
210
|
+
}
|
|
211
|
+
results = append(results, decodedPatchTransactionResult{result: result, raw: rawResult})
|
|
212
|
+
}
|
|
213
|
+
return results, nil
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
func validateApplyResults(
|
|
217
|
+
expected []PatchTransaction,
|
|
218
|
+
actual []decodedPatchTransactionResult,
|
|
219
|
+
allowNonAcknowledgedResults bool,
|
|
220
|
+
) ([]PatchTransactionResult, []json.RawMessage, bool, error) {
|
|
221
|
+
byID := make(map[string]decodedPatchTransactionResult, len(actual))
|
|
222
|
+
for _, decoded := range actual {
|
|
223
|
+
result := decoded.result
|
|
224
|
+
result.TransactionID = strings.TrimSpace(result.TransactionID)
|
|
225
|
+
if result.TransactionID == "" {
|
|
226
|
+
return nil, nil, false, fmt.Errorf("canvas apply returned a result without transaction_id")
|
|
227
|
+
}
|
|
228
|
+
if _, duplicate := byID[result.TransactionID]; duplicate {
|
|
229
|
+
return nil, nil, false, fmt.Errorf("canvas apply returned duplicate result for transaction %q", result.TransactionID)
|
|
230
|
+
}
|
|
231
|
+
decoded.result = result
|
|
232
|
+
byID[result.TransactionID] = decoded
|
|
233
|
+
}
|
|
234
|
+
if len(byID) != len(expected) {
|
|
235
|
+
return nil, nil, false, fmt.Errorf("canvas apply returned %d transaction results, want %d", len(byID), len(expected))
|
|
236
|
+
}
|
|
237
|
+
ordered := make([]PatchTransactionResult, 0, len(expected))
|
|
238
|
+
orderedRaw := make([]json.RawMessage, 0, len(expected))
|
|
239
|
+
hasNonAcknowledgedResult := false
|
|
240
|
+
for _, transaction := range expected {
|
|
241
|
+
decoded, ok := byID[transaction.TransactionID]
|
|
242
|
+
if !ok {
|
|
243
|
+
return nil, nil, false, fmt.Errorf("canvas apply omitted transaction result %q", transaction.TransactionID)
|
|
244
|
+
}
|
|
245
|
+
result := decoded.result
|
|
246
|
+
status := strings.ToLower(strings.TrimSpace(result.Status))
|
|
247
|
+
switch status {
|
|
248
|
+
case "ack":
|
|
249
|
+
for _, patch := range transaction.Patches {
|
|
250
|
+
version, ok := result.AssetVersions[patch.AssetID]
|
|
251
|
+
if !ok {
|
|
252
|
+
if allowNonAcknowledgedResults && patch.Op == "add" && patch.Path == "" {
|
|
253
|
+
continue
|
|
254
|
+
}
|
|
255
|
+
return nil, nil, false, fmt.Errorf("canvas apply transaction %q omitted version for asset %q", transaction.TransactionID, patch.AssetID)
|
|
256
|
+
}
|
|
257
|
+
if version < 0 {
|
|
258
|
+
return nil, nil, false, fmt.Errorf("canvas apply transaction %q returned negative version for asset %q", transaction.TransactionID, patch.AssetID)
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
case "reject", "blocked", "skipped":
|
|
262
|
+
if !allowNonAcknowledgedResults {
|
|
263
|
+
return nil, nil, false, fmt.Errorf(
|
|
264
|
+
"canvas apply transaction %q was not acknowledged: status=%q blocked_by=%q error=%q",
|
|
265
|
+
transaction.TransactionID, result.Status, result.BlockedByTransaction, result.Error,
|
|
266
|
+
)
|
|
267
|
+
}
|
|
268
|
+
hasNonAcknowledgedResult = true
|
|
269
|
+
default:
|
|
270
|
+
return nil, nil, false, fmt.Errorf(
|
|
271
|
+
"canvas apply transaction %q returned invalid status %q",
|
|
272
|
+
transaction.TransactionID, result.Status,
|
|
273
|
+
)
|
|
274
|
+
}
|
|
275
|
+
ordered = append(ordered, result)
|
|
276
|
+
orderedRaw = append(orderedRaw, decoded.raw)
|
|
277
|
+
}
|
|
278
|
+
return ordered, orderedRaw, hasNonAcknowledgedResult, nil
|
|
279
|
+
}
|