@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.
Files changed (50) hide show
  1. package/README.md +41 -2
  2. package/checksums.txt +6 -6
  3. package/cmd/auth/auth.go +136 -142
  4. package/cmd/auth/auth_test.go +134 -0
  5. package/cmd/canvas/canvas.go +241 -0
  6. package/cmd/canvas/canvas_test.go +182 -0
  7. package/cmd/get_credit_balance.go +37 -0
  8. package/cmd/get_credit_balance_test.go +73 -0
  9. package/cmd/root.go +31 -5
  10. package/cmd/root_test.go +69 -0
  11. package/cmd/short_drama_test.go +5 -7
  12. package/cmd/update/update.go +34 -5
  13. package/cmd/update/update_test.go +84 -0
  14. package/dist/checksums.txt +6 -0
  15. package/dist/xyq-canvas-command-runtime.cjs +16 -0
  16. package/dist/xyq-canvas-command-runtime.cjs.LEGAL.txt +599 -0
  17. package/dist/xyq-canvas-command-runtime.cjs.sha256 +2 -0
  18. package/internal/auth/auth_test.go +813 -0
  19. package/internal/auth/browser_darwin.go +14 -0
  20. package/internal/auth/browser_env.go +35 -0
  21. package/internal/auth/browser_linux.go +14 -0
  22. package/internal/auth/browser_windows.go +14 -0
  23. package/internal/auth/identity.go +89 -0
  24. package/internal/auth/loopback.go +324 -0
  25. package/internal/auth/manager.go +338 -144
  26. package/internal/auth/store.go +303 -0
  27. package/internal/auth/store_file_unix.go +176 -0
  28. package/internal/auth/store_file_windows.go +11 -0
  29. package/internal/auth/types.go +72 -0
  30. package/internal/canvas/allocate.go +70 -0
  31. package/internal/canvas/apply.go +279 -0
  32. package/internal/canvas/canvas_test.go +559 -0
  33. package/internal/canvas/create.go +380 -0
  34. package/internal/canvas/get.go +156 -0
  35. package/internal/canvas/types.go +68 -0
  36. package/internal/canvas/upload.go +250 -0
  37. package/internal/common/access_key.go +42 -6
  38. package/internal/common/access_key_test.go +113 -0
  39. package/internal/common/client.go +119 -21
  40. package/internal/common/client_test.go +213 -0
  41. package/internal/common/get_credit_balance.go +61 -0
  42. package/internal/common/get_credit_balance_test.go +75 -0
  43. package/internal/common/runner.go +15 -0
  44. package/internal/config/config.go +11 -28
  45. package/internal/config/config_test.go +3 -18
  46. package/package.json +9 -2
  47. package/scripts/canvas-command.js +881 -0
  48. package/scripts/run.js +21 -4
  49. package/skills/short-drama/SKILL.md +4 -4
  50. 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
+ }