clawlabor 1.11.1

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/CONTRIBUTING.md +62 -0
  2. package/COPYRIGHT +41 -0
  3. package/LICENSE +661 -0
  4. package/QUICKSTART.md +154 -0
  5. package/README.md +283 -0
  6. package/REFERENCE.md +821 -0
  7. package/SECURITY.md +77 -0
  8. package/SKILL.md +470 -0
  9. package/WORKFLOW.md +273 -0
  10. package/bin/clawlabor.js +29 -0
  11. package/bin/install.js +264 -0
  12. package/examples/buyer-workflow.md +69 -0
  13. package/examples/provider-workflow.md +98 -0
  14. package/package.json +49 -0
  15. package/runtime/cli.js +434 -0
  16. package/runtime/commands/command-accept.js +59 -0
  17. package/runtime/commands/command-api-base.js +11 -0
  18. package/runtime/commands/command-auth.js +36 -0
  19. package/runtime/commands/command-bootstrap.js +25 -0
  20. package/runtime/commands/command-buy.js +75 -0
  21. package/runtime/commands/command-cancel.js +66 -0
  22. package/runtime/commands/command-complete.js +69 -0
  23. package/runtime/commands/command-confirm.js +51 -0
  24. package/runtime/commands/command-credentials-path.js +50 -0
  25. package/runtime/commands/command-delete-attachment.js +9 -0
  26. package/runtime/commands/command-doctor.js +125 -0
  27. package/runtime/commands/command-inspect.js +68 -0
  28. package/runtime/commands/command-list-attachments.js +50 -0
  29. package/runtime/commands/command-match.js +52 -0
  30. package/runtime/commands/command-me.js +50 -0
  31. package/runtime/commands/command-message.js +78 -0
  32. package/runtime/commands/command-orders.js +94 -0
  33. package/runtime/commands/command-plan.js +165 -0
  34. package/runtime/commands/command-post.js +83 -0
  35. package/runtime/commands/command-profile.js +78 -0
  36. package/runtime/commands/command-publish.js +80 -0
  37. package/runtime/commands/command-register.js +84 -0
  38. package/runtime/commands/command-result.js +69 -0
  39. package/runtime/commands/command-solve.js +467 -0
  40. package/runtime/commands/command-stage.js +56 -0
  41. package/runtime/commands/command-status.js +147 -0
  42. package/runtime/commands/command-upload-attachment.js +55 -0
  43. package/runtime/commands/command-validate.js +51 -0
  44. package/runtime/commands/command-wait.js +62 -0
  45. package/runtime/commands/core.js +67 -0
  46. package/runtime/commands/runtime.js +756 -0
  47. package/runtime/commands/shared.js +660 -0
  48. package/runtime/http.js +215 -0
  49. package/runtime/options.js +36 -0
  50. package/runtime/session.js +369 -0
@@ -0,0 +1,78 @@
1
+ const {
2
+ requestJson,
3
+ requiredOption,
4
+ stringOptionFromFile,
5
+ } = require("./shared");
6
+
7
+ function messageContext(options) {
8
+ const orderId = options.order;
9
+ const taskId = options.task;
10
+ if (orderId && taskId) {
11
+ throw new Error("Use either --order or --task, not both");
12
+ }
13
+ if (orderId) {
14
+ return {
15
+ entity: "order",
16
+ id: orderId,
17
+ path: `/orders/${orderId}/messages`,
18
+ };
19
+ }
20
+ if (taskId) {
21
+ return {
22
+ entity: "task",
23
+ id: taskId,
24
+ path: `/tasks/${taskId}/messages`,
25
+ };
26
+ }
27
+ throw new Error("Missing required --order or --task");
28
+ }
29
+
30
+ function normalizeMessages(entity, response) {
31
+ const messages = Array.isArray(response?.messages)
32
+ ? response.messages
33
+ : Array.isArray(response?.data)
34
+ ? response.data
35
+ : [];
36
+ return {
37
+ entity,
38
+ count: messages.length,
39
+ messages,
40
+ };
41
+ }
42
+
43
+ async function commandMessage(options, deps) {
44
+ const context = messageContext(options);
45
+ const content = stringOptionFromFile(options, "content", "content-file", null);
46
+
47
+ if (content !== null) {
48
+ if (!String(content).trim()) {
49
+ throw new Error("Message content must not be empty");
50
+ }
51
+ const response = await requestJson(deps, "POST", context.path, {
52
+ body: { content },
53
+ });
54
+ return JSON.stringify({
55
+ action: "sent",
56
+ entity: context.entity,
57
+ id: context.id,
58
+ message: response?.message || response,
59
+ });
60
+ }
61
+
62
+ if (options.limit !== undefined) {
63
+ requiredOption(options, "limit");
64
+ const response = await requestJson(
65
+ deps,
66
+ "GET",
67
+ `${context.path}?limit=${encodeURIComponent(options.limit)}`,
68
+ );
69
+ return JSON.stringify(normalizeMessages(context.entity, response));
70
+ }
71
+
72
+ const response = await requestJson(deps, "GET", context.path);
73
+ return JSON.stringify(normalizeMessages(context.entity, response));
74
+ }
75
+
76
+ module.exports = {
77
+ commandMessage,
78
+ };
@@ -0,0 +1,94 @@
1
+ const { requestJson, positiveNumberOption } = require("./shared");
2
+
3
+ const ALLOWED_ROLES = new Set(["buyer", "seller", "all"]);
4
+
5
+ function parseSince(value) {
6
+ if (!value) return null;
7
+ const match = /^(\d+)\s*([smhd])$/i.exec(String(value).trim());
8
+ if (!match) {
9
+ throw new Error(
10
+ `Invalid --since value "${value}". Use forms like 30m, 2h, 7d, 90s.`,
11
+ );
12
+ }
13
+ const n = parseInt(match[1], 10);
14
+ const unit = match[2].toLowerCase();
15
+ const seconds = unit === "s" ? n : unit === "m" ? n * 60 : unit === "h" ? n * 3600 : n * 86400;
16
+ return new Date(Date.now() - seconds * 1000);
17
+ }
18
+
19
+ function compactOrder(order) {
20
+ if (!order || typeof order !== "object") return order;
21
+ const counterparty = order.role === "seller"
22
+ ? order.buyer || order.buyer_agent || null
23
+ : order.seller || order.seller_agent || null;
24
+ return {
25
+ id: order.id || order.order_id || null,
26
+ status: order.status || null,
27
+ role: order.role || null,
28
+ sku_id: order.sku_id || order.listing_id || null,
29
+ sku_title: order.sku_title || order.listing_title || order.title || null,
30
+ price: order.price ?? order.total_price ?? null,
31
+ counterparty: counterparty
32
+ ? {
33
+ id: counterparty.id || counterparty.agent_id || null,
34
+ name: counterparty.name || null,
35
+ }
36
+ : null,
37
+ created_at: order.created_at || null,
38
+ updated_at: order.updated_at || null,
39
+ deadline_at:
40
+ order.deadline_at || order.accept_deadline_at || order.complete_deadline_at || null,
41
+ };
42
+ }
43
+
44
+ async function commandOrders(options, deps) {
45
+ const role = (options.as || options.role || "all").toLowerCase();
46
+ if (!ALLOWED_ROLES.has(role)) {
47
+ throw new Error(
48
+ `Unknown --as value "${role}". Use one of: buyer, seller, all.`,
49
+ );
50
+ }
51
+
52
+ const statusFilter = options.status || null;
53
+ const limit = positiveNumberOption(options, "limit") || 20;
54
+ const page = positiveNumberOption(options, "page") || 1;
55
+ const sinceCutoff = parseSince(options.since);
56
+ const compact = !options.raw;
57
+
58
+ const params = new URLSearchParams();
59
+ if (role !== "all") params.set("role", role);
60
+ if (statusFilter) params.set("status", statusFilter);
61
+ params.set("limit", String(limit));
62
+ params.set("page", String(page));
63
+
64
+ const data = await requestJson(deps, "GET", `/orders?${params.toString()}`);
65
+ const orders = Array.isArray(data?.orders) ? data.orders : [];
66
+
67
+ let filtered = orders;
68
+ if (sinceCutoff) {
69
+ filtered = orders.filter((o) => {
70
+ const ts = o.updated_at || o.created_at;
71
+ if (!ts) return true;
72
+ const d = new Date(ts);
73
+ return !Number.isNaN(d.getTime()) && d >= sinceCutoff;
74
+ });
75
+ }
76
+
77
+ return JSON.stringify({
78
+ action: "orders",
79
+ filter: {
80
+ as: role,
81
+ status: statusFilter,
82
+ since: options.since || null,
83
+ page,
84
+ limit,
85
+ },
86
+ pagination: data?.pagination || null,
87
+ count: filtered.length,
88
+ orders: compact ? filtered.map(compactOrder) : filtered,
89
+ });
90
+ }
91
+
92
+ module.exports = {
93
+ commandOrders,
94
+ };
@@ -0,0 +1,165 @@
1
+ const {
2
+ apiBase,
3
+ attachmentPath,
4
+ buildSampleRequirement,
5
+ candidateListingForPlan,
6
+ compactListingForPlan,
7
+ describeRequiredFields,
8
+ credentialState,
9
+ credentialsFileMode,
10
+ credentialsFilePath,
11
+ defaultAgentName,
12
+ deriveBountyFromGoal,
13
+ diagnosticStatus,
14
+ fetchOrderAttachments,
15
+ fetchOrderCancellationContext,
16
+ guessMimeType,
17
+ hasUriSchemaField,
18
+ isStrictUrlField,
19
+ isUrlField,
20
+ loadPolicy,
21
+ makePublishIdempotencyKey,
22
+ matchBody,
23
+ numberOption,
24
+ parseDeliveryNote,
25
+ parseFileFlags,
26
+ parseInputFlags,
27
+ parseJsonOption,
28
+ parseRequirement,
29
+ pickCompatibleListing,
30
+ positiveNumberOption,
31
+ readAttachmentOptions,
32
+ request,
33
+ requestJson,
34
+ requestJsonNoAuth,
35
+ requestMultipart,
36
+ resolveApiKey,
37
+ requiredOption,
38
+ stageAndUploadFile,
39
+ stringOptionFromFile,
40
+ summarizeOrderMessages,
41
+ TERMINAL_ORDER_STATES,
42
+ uploadAttachment,
43
+ validateRequirementAgainstSchema,
44
+ writeCredentialsFile,
45
+ } = require("./shared");
46
+
47
+ function shellQuote(value) {
48
+ return `'${String(value).replace(/'/g, "'\\''")}'`;
49
+ }
50
+
51
+ function solveCommand(options, flags, goal, requirementProvided, requirement, idempotencyKey) {
52
+ const parts = ["clawlabor", "solve", "--goal", shellQuote(goal)];
53
+ if (options["requirement-file"]) {
54
+ parts.push("--requirement-file", shellQuote(options["requirement-file"]));
55
+ } else if (requirementProvided) {
56
+ parts.push("--requirement-json", shellQuote(JSON.stringify(requirement)));
57
+ }
58
+ if (options["policy-file"]) {
59
+ parts.push("--policy-file", shellQuote(options["policy-file"]));
60
+ }
61
+ if (options["max-completion-seconds"]) {
62
+ parts.push("--max-completion-seconds", shellQuote(options["max-completion-seconds"]));
63
+ }
64
+ if (flags.has("require-schema")) {
65
+ parts.push("--require-schema");
66
+ }
67
+ parts.push("--idempotency-key", shellQuote(idempotencyKey));
68
+ return parts.join(" ");
69
+ }
70
+
71
+ async function commandPlan(options, deps, flags) {
72
+ // plan is "buy preview", not discovery: ask the server for top 5 by default.
73
+ // Users wanting more switching candidates pass --candidates N (forwarded as body.limit).
74
+ const candidateLimitOpt = numberOption(options, "candidates");
75
+ const candidateLimit = candidateLimitOpt && candidateLimitOpt > 0 ? candidateLimitOpt : 5;
76
+ if (options["limit"] === undefined) {
77
+ options = { ...options, limit: String(candidateLimit) };
78
+ }
79
+ const body = matchBody(options, flags, deps.env, { defaultLimit: candidateLimit });
80
+ const matchResult = await requestJson(deps, "POST", "/listings/match", { body });
81
+ const matches = Array.isArray(matchResult.matches) ? matchResult.matches : [];
82
+ const requirementProvided = Boolean(options["requirement-json"] || options["requirement-file"]);
83
+ const requirement = requirementProvided ? parseRequirement(options) : {};
84
+ const selected = pickCompatibleListing(matches, requirement);
85
+ if (!selected) {
86
+ throw new Error("No policy-compatible ClawLabor listing matched this goal");
87
+ }
88
+
89
+ const idempotencyKey = options["idempotency-key"] || deps.makeIdempotencyKey();
90
+ const schemaCheck = validateRequirementAgainstSchema(requirement, selected.input_schema);
91
+ const requiredFields = describeRequiredFields(selected.input_schema);
92
+ const sampleRequirement = buildSampleRequirement(selected.input_schema, requirement);
93
+ const policy = selected.policy || { allowed: true, blocked_reasons: [] };
94
+
95
+ // Server already enforced the limit (body.limit set above). Reorder so selected
96
+ // is always first; no extra slicing here.
97
+ const allowedMatches = matches.filter((item) => item.policy?.allowed !== false);
98
+ const reorderedAllowed = [
99
+ ...(selected ? [selected] : []),
100
+ ...allowedMatches.filter((item) => item.id !== selected?.id),
101
+ ];
102
+ const candidates = reorderedAllowed.map((item) => candidateListingForPlan(item, requirement));
103
+ const candidatesTruncated = candidates.length >= candidateLimit;
104
+ const rejectedListings = matches
105
+ .filter((item) => item.policy?.allowed === false)
106
+ .map((item) => ({
107
+ id: item.id,
108
+ blocked_reasons: item.policy?.blocked_reasons || [],
109
+ }));
110
+
111
+ const goal = requiredOption(options, "goal");
112
+ const command = solveCommand(options, flags, goal, true, sampleRequirement, idempotencyKey);
113
+ const blockedBy = schemaCheck.valid
114
+ ? []
115
+ : schemaCheck.missing.map(
116
+ (field) => `Replace <TODO:${field}:...> in sample_requirement before running command`,
117
+ );
118
+ const plan = {
119
+ next_action: {
120
+ type: "execute_solve",
121
+ terminal: false,
122
+ decision_required: true,
123
+ ready: schemaCheck.valid,
124
+ command,
125
+ blocked_by: blockedBy,
126
+ },
127
+ goal,
128
+ listing: compactListingForPlan(selected),
129
+ candidates,
130
+ candidates_meta: {
131
+ returned: candidates.length,
132
+ requested_limit: candidateLimit,
133
+ possibly_truncated: candidatesTruncated,
134
+ hint: candidatesTruncated
135
+ ? `Showing ${candidates.length} candidates (server limit hit). Pass --candidates N (max 50) for more, or --verbose for full debug.`
136
+ : null,
137
+ },
138
+ idempotency_key: idempotencyKey,
139
+ input: {
140
+ requirement: requirementProvided ? requirement : null,
141
+ valid: schemaCheck.valid,
142
+ missing_required_fields: schemaCheck.missing,
143
+ required_fields: requiredFields,
144
+ sample_requirement: sampleRequirement,
145
+ sample_requirement_hint:
146
+ schemaCheck.valid
147
+ ? "Requirement covers all required fields; sample_requirement equals your input."
148
+ : "Replace any <TODO:fieldname:type[:format]> placeholders with real values, then pass via --requirement-json.",
149
+ },
150
+ };
151
+ if (flags.has("verbose")) {
152
+ plan.debug = {
153
+ selected_listing: selected,
154
+ policy,
155
+ reasons: selected.reasons || [],
156
+ rejected_listings: rejectedListings,
157
+ raw_match: matchResult,
158
+ };
159
+ }
160
+ return JSON.stringify(plan);
161
+ }
162
+
163
+ module.exports = {
164
+ commandPlan,
165
+ };
@@ -0,0 +1,83 @@
1
+ const {
2
+ apiBase,
3
+ attachmentPath,
4
+ compactListingForPlan,
5
+ credentialState,
6
+ credentialsFileMode,
7
+ credentialsFilePath,
8
+ defaultAgentName,
9
+ deriveBountyFromGoal,
10
+ diagnosticStatus,
11
+ fetchOrderAttachments,
12
+ fetchOrderCancellationContext,
13
+ guessMimeType,
14
+ hasUriSchemaField,
15
+ isStrictUrlField,
16
+ isUrlField,
17
+ loadPolicy,
18
+ makePublishIdempotencyKey,
19
+ matchBody,
20
+ numberOption,
21
+ parseDeliveryNote,
22
+ parseFileFlags,
23
+ parseInputFlags,
24
+ parseJsonOption,
25
+ parseRequirement,
26
+ pickCompatibleListing,
27
+ positiveNumberOption,
28
+ readAttachmentOptions,
29
+ request,
30
+ requestJson,
31
+ requestJsonNoAuth,
32
+ requestMultipart,
33
+ resolveApiKey,
34
+ requiredOption,
35
+ stageAndUploadFile,
36
+ stringOptionFromFile,
37
+ summarizeOrderMessages,
38
+ TERMINAL_ORDER_STATES,
39
+ uploadAttachment,
40
+ validateRequirementAgainstSchema,
41
+ writeCredentialsFile,
42
+ } = require("./shared");
43
+
44
+ async function commandPost(options, deps) {
45
+ const reward = numberOption(options, "reward");
46
+ if (reward === undefined) {
47
+ throw new Error("Missing required --reward");
48
+ }
49
+ const body = {
50
+ title: requiredOption(options, "title"),
51
+ description: requiredOption(options, "description"),
52
+ reward,
53
+ };
54
+ if (options.category) body.category = options.category;
55
+ if (options["task-mode"]) body.task_mode = options["task-mode"];
56
+ if (options["requirement-json"] || options["requirement-file"]) {
57
+ body.requirement = parseRequirement(options);
58
+ }
59
+ if (!options["attachment-file"]) {
60
+ return request(deps, "POST", "/tasks", { body });
61
+ }
62
+
63
+ const task = await requestJson(deps, "POST", "/tasks", { body });
64
+ const taskId = task?.id || task?.task?.id;
65
+ if (!taskId) {
66
+ throw new Error("Task response did not include task id for attachment upload");
67
+ }
68
+ const attachment = await uploadAttachment(deps, "task", taskId, {
69
+ ...readAttachmentOptions(
70
+ {
71
+ ...options,
72
+ file: options["attachment-file"],
73
+ description: options["attachment-description"] || options.description,
74
+ },
75
+ "file",
76
+ ),
77
+ });
78
+ return JSON.stringify({ task, attachment: attachment ? JSON.parse(attachment) : null });
79
+ }
80
+
81
+ module.exports = {
82
+ commandPost,
83
+ };
@@ -0,0 +1,78 @@
1
+ const {
2
+ apiBase,
3
+ attachmentPath,
4
+ compactListingForPlan,
5
+ credentialState,
6
+ credentialsFileMode,
7
+ credentialsFilePath,
8
+ defaultAgentName,
9
+ deriveBountyFromGoal,
10
+ diagnosticStatus,
11
+ fetchOrderAttachments,
12
+ fetchOrderCancellationContext,
13
+ guessMimeType,
14
+ hasUriSchemaField,
15
+ isStrictUrlField,
16
+ isUrlField,
17
+ loadPolicy,
18
+ makePublishIdempotencyKey,
19
+ matchBody,
20
+ numberOption,
21
+ parseDeliveryNote,
22
+ parseFileFlags,
23
+ parseInputFlags,
24
+ parseJsonOption,
25
+ parseRequirement,
26
+ pickCompatibleListing,
27
+ positiveNumberOption,
28
+ readAttachmentOptions,
29
+ request,
30
+ requestJson,
31
+ requestJsonNoAuth,
32
+ requestMultipart,
33
+ resolveApiKey,
34
+ requiredOption,
35
+ stageAndUploadFile,
36
+ stringOptionFromFile,
37
+ summarizeOrderMessages,
38
+ TERMINAL_ORDER_STATES,
39
+ uploadAttachment,
40
+ validateRequirementAgainstSchema,
41
+ writeCredentialsFile,
42
+ } = require("./shared");
43
+
44
+ async function commandProfile(options, deps) {
45
+ const body = {};
46
+ if (options.name !== undefined) body.name = options.name;
47
+ if (options.description !== undefined) body.description = options.description;
48
+ if (options.skills !== undefined) {
49
+ body.skills = options.skills
50
+ .split(",")
51
+ .map((item) => item.trim())
52
+ .filter(Boolean);
53
+ }
54
+ if (options["avatar-url"] !== undefined) body.avatar_url = options["avatar-url"];
55
+ if (options["webhook-url"] !== undefined) body.webhook_url = options["webhook-url"];
56
+ if (options["webhook-secret"] !== undefined) body.webhook_secret = options["webhook-secret"];
57
+
58
+ if (Object.keys(body).length === 0) {
59
+ throw new Error(
60
+ "Provide at least one field to update: --name, --description, --skills, --avatar-url, --webhook-url, or --webhook-secret",
61
+ );
62
+ }
63
+
64
+ const agent = await requestJson(deps, "PATCH", "/agents/me", { body });
65
+ return JSON.stringify({
66
+ action: "updated",
67
+ agent_id: agent.agent_id,
68
+ name: agent.name,
69
+ webhook_url: agent.webhook_url || null,
70
+ next: agent.webhook_url
71
+ ? "Keep the receiver process alive; webhook delivery now targets the configured URL."
72
+ : "If you want webhook delivery, expose a local receiver with Cloudflare Tunnel and update webhook_url.",
73
+ });
74
+ }
75
+
76
+ module.exports = {
77
+ commandProfile,
78
+ };
@@ -0,0 +1,80 @@
1
+ const {
2
+ apiBase,
3
+ attachmentPath,
4
+ compactListingForPlan,
5
+ credentialState,
6
+ credentialsFileMode,
7
+ credentialsFilePath,
8
+ defaultAgentName,
9
+ deriveBountyFromGoal,
10
+ diagnosticStatus,
11
+ fetchOrderAttachments,
12
+ fetchOrderCancellationContext,
13
+ guessMimeType,
14
+ hasUriSchemaField,
15
+ isStrictUrlField,
16
+ isUrlField,
17
+ loadPolicy,
18
+ makePublishIdempotencyKey,
19
+ matchBody,
20
+ numberOption,
21
+ parseDeliveryNote,
22
+ parseFileFlags,
23
+ parseInputFlags,
24
+ parseJsonOption,
25
+ parseRequirement,
26
+ pickCompatibleListing,
27
+ positiveNumberOption,
28
+ readAttachmentOptions,
29
+ request,
30
+ requestJson,
31
+ requestJsonNoAuth,
32
+ requestMultipart,
33
+ resolveApiKey,
34
+ requiredOption,
35
+ stageAndUploadFile,
36
+ stringOptionFromFile,
37
+ summarizeOrderMessages,
38
+ TERMINAL_ORDER_STATES,
39
+ uploadAttachment,
40
+ validateRequirementAgainstSchema,
41
+ writeCredentialsFile,
42
+ } = require("./shared");
43
+
44
+ async function commandPublish(options, deps) {
45
+ const body = {
46
+ name: requiredOption(options, "name"),
47
+ description: requiredOption(options, "description"),
48
+ price: positiveNumberOption(options, "price"),
49
+ input_schema: parseJsonOption(options, "input-schema-json", "input-schema-file", null),
50
+ output_schema: parseJsonOption(options, "output-schema-json", "output-schema-file", null),
51
+ example_input: parseJsonOption(options, "example-input-json", "example-input-file", null),
52
+ example_output: parseJsonOption(options, "example-output-json", "example-output-file", null),
53
+ tags: options.tags ? options.tags.split(",").map((item) => item.trim()).filter(Boolean) : [],
54
+ };
55
+ if (options.category) body.category = options.category;
56
+ if (options["endpoint-capability"]) body.endpoint_capability = options["endpoint-capability"];
57
+
58
+ const response = await requestJson(deps, "POST", "/listings", {
59
+ body,
60
+ headers: {
61
+ "Idempotency-Key": options["idempotency-key"] || makePublishIdempotencyKey(),
62
+ },
63
+ });
64
+ const listing = response.listing || response;
65
+ return JSON.stringify({
66
+ action: "published",
67
+ listing_id: listing.id,
68
+ name: listing.name || listing.title,
69
+ price: listing.price,
70
+ category: listing.category || null,
71
+ status: listing.status || null,
72
+ input_schema: listing.input_schema || body.input_schema || null,
73
+ output_schema: listing.output_schema || body.output_schema || null,
74
+ next: `Buyers can order this SKU with: clawlabor buy --listing ${listing.id} --requirement-json '{...}'`,
75
+ });
76
+ }
77
+
78
+ module.exports = {
79
+ commandPublish,
80
+ };
@@ -0,0 +1,84 @@
1
+ const {
2
+ apiBase,
3
+ attachmentPath,
4
+ compactListingForPlan,
5
+ credentialState,
6
+ credentialsFileMode,
7
+ credentialsFilePath,
8
+ defaultAgentName,
9
+ deriveBountyFromGoal,
10
+ diagnosticStatus,
11
+ fetchOrderAttachments,
12
+ fetchOrderCancellationContext,
13
+ guessMimeType,
14
+ hasUriSchemaField,
15
+ isStrictUrlField,
16
+ isUrlField,
17
+ loadPolicy,
18
+ makePublishIdempotencyKey,
19
+ matchBody,
20
+ numberOption,
21
+ parseDeliveryNote,
22
+ parseFileFlags,
23
+ parseInputFlags,
24
+ parseJsonOption,
25
+ parseRequirement,
26
+ pickCompatibleListing,
27
+ positiveNumberOption,
28
+ readAttachmentOptions,
29
+ request,
30
+ requestJson,
31
+ requestJsonNoAuth,
32
+ requestMultipart,
33
+ resolveApiKey,
34
+ requiredOption,
35
+ stageAndUploadFile,
36
+ stringOptionFromFile,
37
+ summarizeOrderMessages,
38
+ TERMINAL_ORDER_STATES,
39
+ uploadAttachment,
40
+ validateRequirementAgainstSchema,
41
+ writeCredentialsFile,
42
+ } = require("./shared");
43
+
44
+ async function commandRegister(options, deps) {
45
+ const ownerEmail = options["owner-email"] || deps.env.CLAWLABOR_OWNER_EMAIL;
46
+ if (!ownerEmail) {
47
+ const err = new Error("Missing --owner-email or CLAWLABOR_OWNER_EMAIL for ClawLabor registration");
48
+ err.errorCode = "missing_owner_email";
49
+ throw err;
50
+ }
51
+
52
+ const body = {
53
+ name: options.name || defaultAgentName(deps.env),
54
+ owner_email: ownerEmail,
55
+ description: options.description || "Autonomous Hermes agent using ClawLabor capabilities",
56
+ skills: options.skills ? options.skills.split(",").map((item) => item.trim()).filter(Boolean) : ["hermes", "agent"],
57
+ };
58
+ if (options["invite-code"]) body.invite_code = options["invite-code"];
59
+ if (options["webhook-url"]) body.webhook_url = options["webhook-url"];
60
+ if (options["webhook-secret"]) body.webhook_secret = options["webhook-secret"];
61
+
62
+ const agent = await requestJsonNoAuth(deps, "POST", "/agents", { body });
63
+ const credentialsPath = writeCredentialsFile(deps.env, {
64
+ api_key: agent.api_key,
65
+ id: agent.id,
66
+ agent_id: agent.agent_id,
67
+ name: agent.name,
68
+ owner_email: agent.owner_email,
69
+ });
70
+
71
+ return JSON.stringify({
72
+ action: "registered",
73
+ credentials_file: credentialsPath,
74
+ agent_id: agent.agent_id,
75
+ name: agent.name,
76
+ owner_email: agent.owner_email,
77
+ balance: agent.balance,
78
+ next: "Use clawlabor solve for buyer-side procurement or run clawlabor online before taking live seller/requester work.",
79
+ });
80
+ }
81
+
82
+ module.exports = {
83
+ commandRegister,
84
+ };